Architecture Overview
Server Runtime
Rendering Pipeline
Client Navigation
Caching and Export
Development Tools
Build and Configuration
Ecosystem Packages
Testing Infrastructure
How It Works
The following files were used as context for generating this wiki page:
Next.js bundler integration coordinates compilation across multiple underlying bundler backends—specifically supporting Turbopack, Webpack, and Rspack. It manages CLI argument parsing, configuration validation, compiler lifecycle orchestration, and incremental manifest persistence for both development and production targets.
Sources: packages/next/src/lib/bundler.ts:2-87, packages/next/src/server/dev/hot-reloader-webpack.ts:687-787, packages/next/src/shared/lib/turbopack/manifest-loader.ts:177-225
The bundler selection and configuration engine is responsible for parsing command-line options, enumerating supported compilation backends, validating user configurations, and ensuring Turbopack compatibility against unsupported Next.js configuration options. It bridges CLI invocations and the core build system by determining which bundler engine (Turbopack, Webpack, or Rspack) executes for a given command.
Sources: packages/next/src/lib/bundler.ts:1-102, packages/next/src/cli/next-build.ts:13-74, packages/next/src/lib/turbopack-warning.ts:41-190
The selection mechanism revolves around the Bundler enumeration and the parseBundlerArgs function located in packages/next/src/lib/bundler.ts. This engine evaluates explicit CLI flags, environment variables, and test overrides to select the active bundler backend.
Caution
Setting conflicting flags across different bundlers (e.g., passing both --turbopack and --webpack) causes parseBundlerArgs to record multiple entries in bundlerFlags, print an error listing all active flags to console.error, and force an immediate termination via process.exit(1).
Sources: packages/next/src/lib/bundler.ts:65-72
Once the initial bundler is chosen, validateTurboNextConfig parses the user's next.config.js via loadConfig in raw configuration mode and checks for options that are incompatible with Turbopack. It recursively flattens custom configuration keys and compares them against unsupportedTurbopackNextConfigOptions.
Warning
If a build defaults to Turbopack (process.env.TURBOPACK === 'auto') while a webpack configuration property is defined without a corresponding turbopack configuration, Next.js logs an error and terminates execution with process.exit(1). Users can silence this check by supplying an explicit --turbopack or --webpack flag or by declaring an empty turbopack: {} block in their configuration file.
Sources: packages/next/src/lib/turbopack-warning.ts:143-166
The engine tracks a specific set of unsupported Next.js configuration keys when running under Turbopack:
experimental.fetchCacheKeyPrefixexperimental.clientRouterFilterAllowedRateexperimental.allowedRevalidateHeaderKeysexperimental.extensionAliasexperimental.fallbackNodePolyfillsexperimental.swcTraceProfilingexperimental.craCompatexperimental.disablePostcssPresetEnvexperimental.esmExternalsexperimental.forceSwcTransformsexperimental.fullySpecifiedexperimental.urlImportsexperimental.slowModuleDetectionThe bundler selection and validation sequence flows through several stages:
parseBundlerArgs(options) evaluates options.turbopack, options.turbo, options.webpack, and associated environment variables (TURBOPACK, NEXT_RSPACK, etc.), populating bundlerFlags.bundlerFlags.size > 1, parseBundlerArgs rejects the command, logs the conflict, and exits with status 1. If bundlerFlags.size === 0, it defaults to Bundler.Turbopack and sets process.env.TURBOPACK = 'auto'.nextBuild() invokes parseBundlerArgs(options) and performs secondary validations, such as asserting that --experimental-analyze matches exclusively with Bundler.Turbopack.validateTurboNextConfig loads the raw configuration object via loadConfig(configPhase, dir, { rawConfig: true }), flattens its keys, and checks against the unsupported option manifest.Sources: packages/next/src/lib/bundler.ts:15-87, packages/next/src/cli/next-build.ts:68-74, packages/next/src/lib/turbopack-warning.ts:41-76
Development bundler orchestration bridges Next.js server initialization, dynamic route watching, and the underlying compiler lifecycle. The development server instantiates the bundler through setupDevBundler, recording telemetry events and returning a DevBundler interface that exposes request handlers, manifest checkers, and hot-reloading hooks.
Sources: packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:1302-1341, packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:1343-1343
The DevBundlerService wraps the DevBundler instance to perform development-time tasks, manage Incremental Static Regeneration (ISR) manifests via an internal LRUCache, and route HMR communication.
Sources: packages/next/src/server/lib/dev-bundler-service.ts:17-43
Note
The ISR status manifest is selectively transmitted to legacy Pages Router clients or App Router clients with Cache Components disabled. When Cache Components are active, the binary nature of partial static rendering prevents the static indicator manifest from providing granular telemetry. Sources: packages/next/src/server/lib/dev-bundler-service.ts:113-130
DevBundlerService enables programmatic revalidation by mocking Node.js request and response objects, dispatching them through the worker handler, and asserting HTTP cache headers.
Sources: packages/next/src/server/lib/dev-bundler-service.ts:75-101
public async revalidate({
urlPath,
headers,
opts: revalidateOpts,
}: {
urlPath: string
headers: IncomingMessage['headers']
opts: any
}) {
const mocked = createRequestResponseMocks({
url: urlPath,
headers,
})
await this.handler(mocked.req, mocked.res)
await mocked.res.hasStreamed
if (
mocked.res.getHeader('x-nextjs-cache') !== 'REVALIDATED' &&
mocked.res.statusCode !== 200 &&
!(mocked.res.statusCode === 404 && revalidateOpts.unstable_onlyGenerated)
) {
throw new Error(`Invalid response ${mocked.res.statusCode}`)
}
return {}
}The setup routine registers virtual file system items (devVirtualFsItems) for client pages manifests and middleware matchers, intercepting incoming HTTP requests inside requestHandler before they reach downstream routing logic.
Sources: packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:1221-1258
The request handling sequence processes incoming HTTP messages through distinct stages:
requestHandler invokes parseUrl(req.url || '/') to extract the request pathname.pathname includes clientPagesManifestPath, the server responds with status 200, sets Content-Type: application/json, and serializes non-App Router routes filtered via opts.fsChecker.appFiles.pathname matches devMiddlewareManifestPath or devTurbopackMiddlewareManifestPath, it responds with serverFields.middleware?.matchers and returns { finished: true }.{ finished: false } to let standard routing handle the request.Warning
When logErrorWithOriginalStack processes runtime errors, it deobfuscates error messages and checks instance types: ModuleBuildError logs standard error output via Log.error(err.message), whereas TurbopackInternalError suppresses raw console output since rust-side handlers already write simplified messages to disk.
Sources: packages/next/src/server/lib/router-utils/setup-dev-bundler.ts:1260-1282
The Webpack and Rspack compilation lifecycle handles bundler resolution, runtime configuration generation for multi-compiler setups, hot reloader initialization, and Node.js require-hook patching. Next.js isolates its internal bundling dependencies by re-routing module requests through custom resolution layers and runtime configuration generators.
Sources: packages/next/src/server/dev/hot-reloader-webpack.ts:687-787, packages/next/src/shared/lib/get-webpack-bundler.ts:1-12, packages/next/src/server/config-utils.ts:1-144
Next.js provides a unified access layer for selecting between standard Webpack and Rspack via getWebpackBundler(). When process.env.NEXT_RSPACK is active, it loads Rspack core via getRspackCore(); otherwise, it returns standard Webpack.
export default function getWebpackBundler(): typeof webpack {
return process.env.NEXT_RSPACK ? getRspackCore() : webpack
}Sources: packages/next/src/shared/lib/get-webpack-bundler.ts:1-12, packages/next/src/bundles/webpack/packages/webpack.js:5-11
To prevent version mismatch issues with user-installed packages, loadWebpackHook() patches the Node.js require function to route webpack and internal loader requests directly to Next.js's bundled webpack versions and plugins.
export function loadWebpackHook() {
if (installed) {
return
}
installed = true
;(
require('../server/require-hook') as typeof import('../server/require-hook')
).addHookAliases(
[
['webpack', 'next/dist/compiled/webpack/webpack-lib'],
['webpack/package', 'next/dist/compiled/webpack/package'],
['webpack/package.json', 'next/dist/compiled/webpack/package'],
['webpack/lib/webpack', 'next/dist/compiled/webpack/webpack-lib'],
['webpack/lib/webpack.js', 'next/dist/compiled/webpack/webpack-lib'],
[
'webpack/lib/node/NodeEnvironmentPlugin',
'next/dist/compiled/webpack/NodeEnvironmentPlugin',
],
].map(
([request, replacement]) => [request, require.resolve(replacement)]
)
)
}Note
loadWebpackHook() uses dynamic require.resolve lookups mapped over array pairs to ensure replacement targets resolve to valid built artifacts within next/dist/compiled/webpack/.
Sources: packages/next/src/server/config-utils.ts:1-144
The getWebpackConfig method orchestrates the creation of multi-compiler configurations for client, server, and edge-server runtimes.
The configuration generation sequence proceeds through distinct phases:
getWebpackConfig executes findPageFile concurrently for /_app and /_document files if a pages directory exists.createPagesMapping to build page definitions using PAGE_TYPES.PAGES.createEntrypoints passing the collected pages, app directory state, and preview configuration properties.loadProjectInfo.getBaseWebpackConfig. private async getWebpackConfig(span: Span) {
const webpackConfigSpan = span.traceChild('get-webpack-config')
const pageExtensions = this.config.pageExtensions
return webpackConfigSpan.traceAsyncFn(async () => {
const pagePaths = !this.pagesDir
? ([] as (string | null)[])
: await webpackConfigSpan
.
Sources: packages/next/src/server/dev/hot-reloader-webpack.ts:886-897, packages/next/src/server/config-utils.ts:1-144
The on-demand entrypoint resolution pipeline governs lazy page compilation scheduling, inactive entry disposal, and batch invalidation control during development. The Invalidator class coordinates compiler triggering to ensure that concurrent invalidation requests are batched without forcing unintended client-side hard reloads due to unstable Webpack hashes.
The Invalidator class manages compilation states using building and rebuildAgain trackers (BuildingTracker and RebuildTracker, mapped to CompilerNameValues). When an invalidation is requested via invalidate(compilerKeys), the execution pipeline follows a precise conditional sequence:
invalidate(compilerKeys)
→ checks if compilerKey is in building Set
→ [If building] adds key to rebuildAgain Set and continues
→ [If idle] adds key to building Set
→ calls multiCompiler.compilers[COMPILER_INDEXES[key]].watching?.invalidate()
When compilation completes, doneBuilding(compilerKeys) clears the keys from building and checks rebuildAgain, automatically re-triggering invalidate(rebuild) if queued updates exist.
Note
If a build is actively processing a compiler key when an invalidation arrives, Invalidator never aborts the active build. Aborting an active build would trigger a client-side hard reload; instead, the key is registered in rebuildAgain and flushed immediately upon completion.
Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:286-296
Active entries are tracked through entriesMap, which indexes entry objects by output directory and entry name. The findPagePathData function normalizes page routes and resolves them to absolute paths using project extensions and directory configurations.
export async function findPagePathData(
rootDir: string,
page: string,
extensions: string[],
pagesDir: string | undefined,
appDir: string | undefined,
isGlobalNotFoundEnabled: boolean
): Promise<PagePathData>Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:233-254, packages/next/src/server/dev/on-demand-entry-handler.ts:400-408
Inactive entries are periodically evaluated by disposeInactiveEntries, which flags entries for removal if their lastActiveTime exceeds maxInactiveAge. Root middleware, instrumentation hooks, and currently active client or server access pages are explicitly excluded from periodic disposal.
Warning
Middleware and instrumentation hook files identified by isMiddlewareFilename(bundlePath) or isInstrumentationHookFilename(bundlePath) are permanently exempt from periodic inactive disposal. Disposing them would break request handling for subsequent requests requiring these handlers.
Sources: packages/next/src/server/dev/on-demand-entry-handler.ts:342-348
The Turbopack runtime and manifest subsystem manages route dispatching, HMR event streaming, issue tracking, and incremental persistence of build artifacts. The TurbopackManifestLoader class coordinates write operations across build, page, client build, app paths, action, font, middleware, and subresource integrity manifests using an internal change-tracking cache layer (ManifestsMap).
Sources: packages/next/src/server/dev/turbopack-utils.ts:146-150, packages/next/src/shared/lib/turbopack/manifest-loader.ts:134-175
Route compilation and dispatching are executed via handleRouteType, which processes different route variants such as 'page', 'page-api', 'app-page', and 'app-route'. When loading middleware or server references for these routes, path resolution relies on precise manifest lookup sequences.
Sources: packages/next/src/server/dev/turbopack-utils.ts:178-434, packages/next/src/shared/lib/turbopack/manifest-loader.ts:71-118
handleRouteType invokes loadMiddlewareManifest to resolve edge runtimes and associated entry points.
Sources: packages/next/src/server/dev/turbopack-utils.ts:237-238, packages/next/src/shared/lib/turbopack/manifest-loader.ts:657-667loadMiddlewareManifest calls getManifestPath to locate the target artifact on disk.
Sources: packages/next/src/shared/lib/turbopack/manifest-loader.ts:661-667getManifestPath invokes addMetadataIdToRoute to format metadata route file paths.
Sources: packages/next/src/shared/lib/turbopack/manifest-loader.ts:113-113getManifestPath invokes addRouteSuffix to append the required route file boundary suffix.
Sources: packages/next/src/shared/lib/turbopack/manifest-loader.ts:113-113handleRouteType invokes loadPagesManifest to record server page mappings.
Sources: packages/next/src/server/dev/turbopack-utils.ts:204-204, packages/next/src/shared/lib/turbopack/manifest-loader.ts:817-822loadPagesManifest delegates to ManifestsMap.set to update raw and parsed json objects.
Sources: packages/next/src/shared/lib/turbopack/manifest-loader.ts:818-818, packages/next/src/shared/lib/turbopack/manifest-loader.ts:141-145ManifestsMap.set uses ManifestsMap.get to evaluate existing map state during updates.
Sources: