Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts
- packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts
- packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts
- packages/next/src/shared/lib/turbopack/manifest-loader.ts
- packages/next/src/build/webpack/plugins/next-trace-entrypoints-plugin.ts
- packages/next/src/build/webpack/plugins/build-manifest-plugin.ts
- packages/next/src/build/webpack/plugins/middleware-plugin.ts
- packages/next/src/build/webpack/plugins/react-loadable-plugin.ts
- packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts
- packages/next/src/cli/internal/static-routes-info.ts
- packages/next/src/server/route-modules/route-module.ts
- packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts
- packages/next/src/build/handle-entrypoints.ts
- packages/next/src/server/dev/turbopack-utils.ts
- packages/next/src/build/utils.ts
- packages/next/src/build/webpack/plugins/subresource-integrity-plugin.ts
- packages/next/src/build/webpack/plugins/rspack-flight-client-entry-plugin.ts
- packages/next/src/build/webpack/plugins/wellknown-errors-plugin/index.ts
Overview
Manifest Plugins form the backbone of Next.js asset management and runtime dependency resolution by intercepting compiler compilation hooks to generate, serialize, and persist critical JSON and JavaScript manifest files. These plugins bridge static bundling output and dynamic server or client execution, enabling featuresBuild Pipeline such as server component client mapping, font preloading tables, dynamic import chunk tracking, and edge runtime route bindings across Webpack and Turbopack Engine compilation pipelines. Sources: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:206-216, packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:55-61, packages/next/src/build/webpack/plugins/build-manifest-plugin.ts:102-128
Build Manifest Compilation and Asset Generation
Initialization and Compilation Hook Registration
BuildManifestPlugin orchestrates asset collection and serialization by tapping into Webpack compilation lifecycles. During initialization, the constructor accepts configuration options—buildId, rewrites, isDevFallback, appDirEnabled, and clientRouterFilters—and normalizes custom route rewrites via processRoute across beforeFiles, afterFiles, and fallback arrays.
export default class BuildManifestPlugin {
private buildId: string
private rewrites: CustomRoutes['rewrites']
private isDevFallback: boolean
private appDirEnabled: boolean
private clientRouterFilters?: Parameters<typeof generateClientManifest>[2]
// ...Sources: packages/next/src/build/webpack/plugins/build-manifest-plugin.ts:102-128
The plugin registers its compilation lifecycle within the compiler's make hook, tapping into processAssets at stage webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS to execute createAssets.
apply(compiler: webpack.Compiler) {
compiler.hooks.make.tap('NextJsBuildManifest', (compilation: any) => {
compilation.hooks.processAssets.tap(
{
name: 'NextJsBuildManifest',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
() => {
this.createAssets(compiler, compilation)
}
)
})
return
}Sources: packages/next/src/build/webpack/plugins/build-manifest-plugin.ts:266-279
Asset Collection and Entrypoint Processing
Within createAssets, the plugin initializes an empty asset map tracking polyfills, development files, low-priority bundles, root main files, and page-specific dependency arrays. It filters out .hot-update.js extensions from entrypoint files via getEntrypointFiles and standardizes path separators to forward slashes.
export function getEntrypointFiles(entrypoint: any): string[] {
return (
entrypoint
?.getFiles()
.filter((file: string) => {
// We don't want to include `.hot-update.js` files into the initial page
return /(?<!\.hot-update)\.(js|css)($|\?)/.test(file)
})
.map((file: string) => file.replace(/\\/g, '/')) ?? []
)
}Sources: packages/next/src/build/webpack/plugins/build-manifest-plugin.ts:88-98
Polyfill files are extracted by iterating over compilation assets where .js files carry the CLIENT_STATIC_FILES_RUNTIME_POLYFILLS_SYMBOL info property. React refresh development dependencies are isolated by filtering entrypoints against the main runtime chunk. Non-system entrypoints are mapped to their respective route paths using getRouteFromEntrypoint.
Note
_app dependencies are collected separately as the baseline set. When generating the reduced client build manifest via generateClientManifest, dependencies already present in /_app are stripped from individual page asset arrays to prevent redundant loading during client-side navigations.
export function generateClientManifest(
assetMap: BuildManifest,
rewrites: CustomRoutes['rewrites'],
clientRouterFilters?: {
staticFilter: ReturnType<BloomFilter['export']>
dynamicFilter: ReturnType<BloomFilter['export']>
},
compiler?: any,
compilation?: any
): string | undefined {
// ...Sources: packages/next/src/build/webpack/plugins/build-manifest-plugin.ts:36-45
Serialization and Asset Emission
Once the asset map is compiled and sorted alphabetically by route key, the plugin emits multiple manifest artifacts into the Webpack compilation output using sources.RawSource.
| Manifest Asset Target | Emitter Path / Name | Serialization Format / Utility |
|---|---|---|
| Main Build Manifest | buildManifestName (build-manifest.json or fallback-build-manifest.json) | JSON.stringify(assetMap, null, 2) |
| Middleware Edge Manifest | server/${MIDDLEWARE_BUILD_MANIFEST}.js | createEdgeRuntimeManifest(assetMap) |
| Client Build Manifest | static/${buildId}/_buildManifest.js | self.__BUILD_MANIFEST = ${generateClientManifest(...)} |
| SSG Manifest | static/${buildId}/_ssgManifest.js | srcEmptySsgManifest |
Sources: packages/next/src/build/webpack/plugins/build-manifest-plugin.ts:229-261, packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts:11-58
Caution
External rewrite destinations lacking a leading slash are actively pruned from destination properties during route processing so that client-side routers do not attempt invalid relative resolutions. Sources: packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts:25-34
export const processRoute = (r: Rewrite) => {
const rewrite = { ...r }
// omit external rewrite destinations since these aren't
// handled client-side
if (!rewrite?.destination?.startsWith('/')) {
delete (rewrite as any).destination
}
return rewrite
}Sources: packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts:25-34
Flight Manifest and Client Reference Resolution
Flight Manifest Plugin State and Structures
const pluginState = getProxiedPluginState({
ssrModules: {} as { [ssrModuleId: string]: ModuleInfo },
edgeSsrModules: {} as { [ssrModuleId: string]: ModuleInfo },
rscModules: {} as { [rscModuleId: string]: ModuleInfo },
edgeRscModules: {} as { [rscModuleId: string]: ModuleInfo },
})Sources: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:46-52
const pluginState = getProxiedPluginState({
// A map to track "action" -> "list of bundles".
serverActions: {} as ActionManifest['node'],
edgeServerActions: {} as ActionManifest['edge'],
serverActionModules: {} as {
[workerName: string]: {
server?: ModuleInfo
client?: ModuleInfo
}
},
edgeServerActionModules: {} as {
[workerName: string]: {
server?: ModuleInfo
client?: ModuleInfo
}
},
ssrModules: {} as { [ssrModuleId: string]: ModuleInfo },
edgeSsrModules: {} as { [ssrModuleId: string]: ModuleInfo },
rscModules: {} as { [rscModuleId: string]: ModuleInfo },
edgeRscModules: {} as { [rscModuleId: string]: ModuleInfo },
injectedClientEntries: {} as Record<string, string>,
})Sources: packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:115-141
The client reference resolution pipeline uses proxied plugin states to aggregate module information across compilation targets. ClientReferenceManifestPlugin builds mapping structures that connect Server Components, Server-Side Rendered (SSR) modules, and Edge runtime modules to their corresponding Webpack chunk groups and file lists.
export interface ManifestNode {
[moduleExport: string]: {
id: ModuleId
name: string
chunks: ManifestChunks
async?: boolean
}
}
export interface ClientReferenceManifestForRsc {
clientModules: ManifestNode
rscModuleMapping: {
[moduleId: string]: ManifestNode
}
edgeRscModuleMapping: {
[moduleId: string]: ManifestNode
}
}Sources: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:54-84
Note
Client reference chunks are encoded as URIs via encodeURIPath and augmented with asset token queries so that React's Flight runtime manages filenames and reserved characters (such as [ and ]) correctly for servers like S3.
Sources: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:141-147
Chunk Grouping and Entry Normalization
Entry names are normalized to group names via entryNameToGroupName to ensure that parallel routes, loading UI, and page components sharing a route segment merge their manifests correctly.
function entryNameToGroupName(entryName: string) {
let groupName = entryName
.slice(0, entryName.lastIndexOf('/'))
// Remove slots
.replace(/\/@[^/]+/g, '')
// Remove the group with lookahead to make sure it's not interception route
.replace(/\/\([^/]+\)(?=(\/|$))/g, '')
// Remove catch-all routes since they should be part of the parent group that the catch-all would apply to.
// This is necessary to support parallel routes since multiple page components can be rendered on the same page.
// In order to do that, we need to ensure that the manifests are merged together by putting them in the same group.
.replace(/\/\[?\[\.\.\.[^\]]*]]?/g, '')
// Interception routes
groupName = groupName
.replace(/^.+\/\(\.\.\.\)/g, 'app/')
.replace(/\/\(\.\)/g, '/')
// Interception routes (recursive)
while (/\/[^/]+\/\(\.\.\)/.test(groupName)) {
groupName = groupName.replace(/\/[^/]+\/\(\.\.\)/g, '/')
}
return groupName
}Sources: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:161-184
When extracting required chunks from chunk groups via getAppPathRequiredChunks, non-JavaScript files, hot updates, system entrypoints, and files present in the root main bundle (excludedFiles) are omitted.
| Parameter / Helper | Target / Behavior | Purpose |
|---|---|---|
chunkGroup.chunks | webpack.ChunkGroup | Iterates through assigned chunks to extract module IDs and asset files |
SYSTEM_ENTRYPOINTS | Set<string> | Skips internal system chunks from being duplicated in route-level manifests |
excludedFiles | Set<string> | Omits root main files already loaded globally by the main application entrypoint |
encodeURIPath | string | Encodes path components for compatibility with remote asset servers |
Sources: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:118-150
Warning
Inlined CSS files are only bundled directly into the manifest when experimentalInlineCss is enabled and dev mode is false, because inline CSS does not currently function properly with Hot Module Replacement (HMR).
Sources: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:299-315
Pages Manifest Mapping and Disk Persistence
Pages Manifest Plugin Mechanics
PagesManifestPlugin implements webpack.WebpackPluginInstance to construct route mapping files such as pages-manifest.json and app-paths-manifest.json from Webpack compilation entrypoints. During the make compilation hook, it registers a processAssets hook at stage webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS to execute createAssets.
apply(compiler: webpack.Compiler): void {
compiler.hooks.make.tap('NextJsPagesManifest', (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: 'NextJsPagesManifest',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
() => this.createAssets(compilation)
)
})
}Sources: packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts:181-191
The plugin iterates over compilation entrypoints, mapping each entry name via getRouteFromEntrypoint. It filters out files containing webpack-runtime or webpack-api-runtime, and retains only those ending in .js.
async createAssets(compilation: any) {
const entrypoints = compilation.entrypoints
const pages: PagesManifest = {}
const appPaths: PagesManifest = {}
for (const entrypoint of entrypoints.values()) {
const pagePath = getRouteFromEntrypoint(
entrypoint.name,
this.appDirEnabled
)
if (!pagePath) {
continue
}
const files = entrypoint
.getFiles()
.filter(
(file: string) =>
!file.includes('webpack-runtime') &&
!file.includes('webpack-api-runtime') &&
file.endsWith('.js')
)
// Skip entries which are empty
if (!files.length) {
continue
}Sources: packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts:46-73
| Property / Option | Type | Purpose |
|---|---|---|
dev | boolean | Indicates whether compilation runs in development mode |
distDir | string | undefined | Target distribution directory for writing persistent disk assets |
isEdgeRuntime | boolean | Specifies if compilation targets the Edge runtime versus Node.js |
appDirEnabled | boolean | Controls whether the App Directory (app/) route mapping is active |
Sources: packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts:24-44
Note
In production non-Edge builds, the initial three characters (.js) are sliced off production entry filenames before normalization, whereas development mode and Edge runtime builds retain the full file path.
Sources: packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts:77-83
Multi-Compiler Manifest Merging and Disk Persistence
Because Next.js utilizes parallel compilers for the Node server and Edge server, PagesManifestPlugin stores module entries in module-level global variables (edgeServerPages, nodeServerPages, edgeServerAppPaths, nodeServerAppPaths) and merges them upon asset creation.
export let edgeServerPages = {}
export let nodeServerPages = {}
export let edgeServerAppPaths = {}
export let nodeServerAppPaths = {}Sources: packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts:13-16
When distDir is specified, writeMergedManifest ensures parent directories via fs.mkdir with recursive options, reads any existing manifest JSON from disk, merges existing entries with new entries, and writes the JSON payload back out with an indentation of 2 spaces. If distDir is absent, assets are emitted directly to Webpack compilation via compilation.emitAsset using sources.RawSource.
// handle parallel compilers writing to the same
// manifest path by merging existing manifest with new
const writeMergedManifest = async (
manifestPath: string,
entries: Record<string, string>
) => {
await fs.mkdir(path.dirname(manifestPath), { recursive: true })
await fs.writeFile(
manifestPath,
JSON.stringify(
{
...(await fs
.readFile(manifestPath, 'utf8')
.then((res) => JSON.parse(res))
.catch(() => ({}))),
...entries,
},
null,
2
)
)
}Sources: packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts:101-122
Warning
Parallel compiler instances write to the same target manifest paths on disk, making the file read-merge-write cycle inside writeMergedManifest necessary to prevent race conditions from overwriting entries generated by separate compiler targets.
Sources: packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts:101-122
Font Manifest Optimization and Preloading Tables
Module Traversal and Font Asset Filtering
The NextFontManifestPlugin implements webpack.WebpackPluginInstance to track font assets generated by next-font-loader, producing optimized font preloading tables and size-adjust metadata for both the pages/ and app/ directory architectures. During the make compilation hook, it hooks into processAssets at stage webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS to inspect module assets and chunk entrypoints.
export class NextFontManifestPlugin {
private appDir: undefined | string
constructor(options: { appDir: undefined | string }) {
this.appDir = options.appDir
}
apply(compiler: webpack.Compiler) {
compiler.hooks.make.tap(PLUGIN_NAME, (compilation) => {
// In this stage the font files are emitted and we can collect all files emitted by each chunkGroup (entry).
compilation.hooks.processAssets.tap(
{
name: PLUGIN_NAME,
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
() => {
const nextFontManifest: NextFontManifest = {
pages: {},
app: {},
appUsingSizeAdjust: false,
pagesUsingSizeAdjust: false,
}Sources: packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:55-76
For app/ directory builds, the plugin uses traverseModules to iterate over modules created within chunk groups whose names start with app/. It checks each module request for /next-font-loader/index.js? and extracts emitted font files ending with .woff, .woff2, .eot, .ttf, or .otf from mod.buildInfo.assets. For pages/ directory entries, it loops through compilation entrypoints, resolves the route path via getRouteFromEntrypoint, and inspects chunk.auxiliaryFiles across all chunks assigned to the entrypoint.
if (this.appDir) {
const appDirBase = path.dirname(this.appDir) + path.sep
// After all modules are created, we collect the modules that was created by next-font-loader.
traverseModules(
compilation,
(mod, _chunk, chunkGroup) => {
if (mod?.request?.includes('/next-font-loader/index.js?')) {
if (!mod.buildInfo?.assets) return
const chunkEntryName = (appDirBase + chunkGroup.name).replace(
/[\\/]/g,
path.sep
)
const modAssets = Object.keys(mod.buildInfo.assets)
const fontFiles: string[] = modAssets.filter((file: string) =>
/\.(woff|woff2|eot|ttf|otf)$/.test(file)
)
// Look if size-adjust fallback font is being used
if (!nextFontManifest.appUsingSizeAdjust) {
nextFontManifest.appUsingSizeAdjust =
getPageIsUsingSizeAdjust(fontFiles)
}
const preloadedFontFiles = getPreloadedFontFiles(fontFiles)
// Add an entry of the module's font files in the manifest.
// We'll add an entry even if no files should preload.
// When an entry is present but empty, instead of preloading the font files, a preconnect tag is added.
if (fontFiles.length > 0) {
if (!nextFontManifest.app[chunkEntryName]) {
nextFontManifest.app[chunkEntryName] = []
}
nextFontManifest.app[chunkEntryName].push(
...preloadedFontFiles
)
}
}
},
(chunkGroup) => {
// Only loop through entrypoints that are under app/.
return !!chunkGroup.name?.startsWith('app/')
}
)
}
// Look at all the entrypoints created for pages/.
for (const entrypoint of compilation.entrypoints.values()) {
const pagePath = getRouteFromEntrypoint(entrypoint.name!)
if (!pagePath) {
continue
}
// Get font files from the chunks included in the entrypoint.
const fontFiles: string[] = entrypoint.chunks
.flatMap((chunk: any) => [...chunk.auxiliaryFiles])
.filter((file: string) =>
/\.(woff|woff2|eot|ttf|otf)$/.test(file)
)
// Look if size-adjust fallback font is being used
if (!nextFontManifest.pagesUsingSizeAdjust) {
nextFontManifest.pagesUsingSizeAdjust =
getPageIsUsingSizeAdjust(fontFiles)
}
const preloadedFontFiles = getPreloadedFontFiles(fontFiles)
// Add an entry of the route's font files in the manifest.
// We'll add an entry even if no files should preload.
// When an entry is present but empty, instead of preloading the font files, a preconnect tag is added.
if (fontFiles.length > 0) {
nextFontManifest.pages[pagePath] = preloadedFontFiles
}
}Sources: packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:78-155
| Helper Function | Target Extension / Pattern | Purpose |
|---|---|---|
getPreloadedFontFiles | /\.p\.(woff|woff2|eot|ttf|otf)$/ | Filters font files to return only those designated for preloading (.p.) |
getPageIsUsingSizeAdjust | file.includes('-s') | Detects whether fallback fonts incorporate size-adjust flags for layout shift metrics |
Sources: packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:26-39
Note
When a module or entrypoint contains font files but none are marked with the preloading flag (.p.), the manifest retains an empty array for that route or module key. During rendering in _document.tsx or app-render, an empty array triggers a preconnect tag instead of a preload link tag for the font family.
Sources: packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:107-116
Manifest Serialization and Asset Emission
The resulting NextFontManifest structure organizes preloading configuration and size-adjust booleans into separate maps for pages and app routes. The plugin serializes this manifest into two separate compilation assets using sources.RawSource: a JSON file written to server/${NEXT_FONT_MANIFEST}.json for Node server consumption, and a JavaScript assignment wrapping self.__NEXT_FONT_MANIFEST written to server/${NEXT_FONT_MANIFEST}.js for the Edge Runtime.
export type NextFontManifest = {
pages: {
[path: string]: string[]
}
app: {
[entry: string]: string[]
}
appUsingSizeAdjust: boolean
pagesUsingSizeAdjust: boolean
}Sources: packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:7-16
const manifest = JSON.stringify(nextFontManifest, null)
// Create manifest for edge
compilation.emitAsset(
`server/${NEXT_FONT_MANIFEST}.js`,
new sources.RawSource(
`self.__NEXT_FONT_MANIFEST=${JSON.stringify(manifest)}`
) as unknown as webpack.sources.RawSource
)
// Create manifest for server
compilation.emitAsset(
`server/${NEXT_FONT_MANIFEST}.json`,
new sources.RawSource(
manifest
) as unknown as webpack.sources.RawSource
)
}Sources: packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:157-173
Warning
The app manifest map stores entries keyed by normalized webpack module chunk entry paths (appDirBase + chunkGroup.name), whereas the pages manifest map uses resolved route paths (pagePath), reflecting the differing module-level versus entrypoint-level resolution strategies required by the two routing models.
Sources: packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:50-54
Loadable Components and Dynamic Import Manifests
Tracking Dynamic Imports and Webpack Chunk Blocks
The ReactLoadablePlugin extracts dynamic import associations during Webpack compilation to build the React Loadable manifest. Hooking into compilation.hooks.processAssets at webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, the plugin executes buildManifest to traverse compilation modules and dependency blocks.
apply(compiler: webpack.Compiler) {
compiler.hooks.make.tap('ReactLoadableManifest', (compilation) => {
compilation.hooks.processAssets.tap(
{
name: 'ReactLoadableManifest',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
},
() => {
this.createAssets(compiler, compilation)
}
)
})
}Sources: packages/next/src/build/webpack/plugins/react-loadable-plugin.ts:239-251
It inspects dependency blocks recursively via handleBlock, matching dependencies whose types start with import(). Standard eager imports or context dependencies (import(/* webpackMode: "eager" */ ...) or import(./module/${param})) are unsupported and skipped.
// Find all dependencies blocks which contains a `import()` dependency
const handleBlock = (block: any) => {
block.blocks.forEach(handleBlock)
const chunkGroup = getChunkGroupFromBlock(compilation, block)
for (const dependency of block.dependencies) {
if (dependency.type.startsWith('import()')) {Sources: packages/next/src/build/webpack/plugins/react-loadable-plugin.ts:83-88
Caution
The plugin explicitly ignores ImportEagerDependency and ImportContextDependency types. Dynamic expressions with template literals or eager webpack modes will fail to register in the loadable manifest, breaking server-side chunk preloading for those components.
Sources: packages/next/src/build/webpack/plugins/react-loadable-plugin.ts:79-82
Manifest Compilation and Asset Generation
For every matched import() dependency, the plugin derives an origin module request from compilation.moduleGraph.getParentModule(dependency) and constructs a unique manifest key using the relative path from projectSrcDir combined with the dependency request string.
// get the module containing the import()
const originModule = getOriginModuleFromDependency(
compilation,
dependency
)
const originRequest: string | undefined = originModule?.resource
if (!originRequest) return
// We construct a "unique" key from origin module and request
// It's not perfect unique, but that will be fine for us.
// We also need to construct the same in the babel plugin.
const key = `${path.relative(projectSrcDir, originRequest)} -> ${
dependency.request
}`Sources: packages/next/src/build/webpack/plugins/react-loadable-plugin.ts:93-106
It then resolves chunk groups associated with the block via compilation.chunkGraph.getBlockChunkGroup(block) and gathers all generated .js and .css files residing under static/chunks/ or static/css/.
if (chunkGroup) {
for (const chunk of (chunkGroup as any)
.chunks as webpack.Compilation['chunks']) {
chunk.files.forEach((file: string) => {
if (
(file.endsWith('.js') || file.endsWith('.css')) &&
file.match(/^static\/(chunks|css)\//)
) {
files.add(file)
if (shouldCreateDynamicCssManifest && file.endsWith('.css')) {
dynamicCssManifestSet.add(file)
}
}
})
}
}Sources: packages/next/src/build/webpack/plugins/react-loadable-plugin.ts:125-141
| Configuration Property / Asset | Target Destination | Purpose |
|---|---|---|
reactLoadableManifest | react-loadable-manifest.json | Maps dynamic import keys to Webpack module IDs and assigned chunk files |
runtimeAsset | Server runtime bundle | Injects self.__REACT_LOADABLE_MANIFEST for Edge Runtime execution |
dynamicCssManifest | dynamic-css-manifest.json | Tracks dynamic CSS assets to prevent removing server-rendered <link> tags after client navigation |
| Edge CSS Asset | server/dynamic-css-manifest.js | Assigns self.__DYNAMIC_CSS_MANIFEST for Edge Runtime stylesheet persistence |
Sources: packages/next/src/build/webpack/plugins/react-loadable-plugin.ts:202-236
Note
In development mode (this.dev), the manifest uses the string key (key) as the module identifier instead of the numeric Webpack module ID (compilation.chunkGraph.getModuleId(module)), ensuring stable identifiers across hot module reloading iterations.
Sources: packages/next/src/build/webpack/plugins/react-loadable-plugin.ts:147-149
Middleware Manifest and Edge Function Assets
Route Matching Regex Generation and Asset Binding
The middleware compilation pipeline coordinates route regex generation and metadata binding for middleware and Edge functions through MiddlewarePlugin. During asset creation, the plugin iterates over all Webpack entrypoints to locate metadata associated with edgeMiddleware, edgeSSR, or edgeApiFunction. For each matched entrypoint, it normalizes App Router paths or page paths and invokes getNamedMiddlewareRegex to build precise route matching expressions.
const matcherSource = metadata.edgeSSR?.isAppDir
? normalizeAppPath(page)
: page
const catchAll = !metadata.edgeSSR && !metadata.edgeApiFunction
const { namedRegex } = getNamedMiddlewareRegex(matcherSource, {
catchAll,
})
const matchers = metadata?.edgeMiddleware?.matchers ?? [
{
regexp: namedRegex,
originalSource: page === '/' && catchAll ? '/:path*' : matcherSource,
},
]Sources: packages/next/src/build/webpack/plugins/middleware-plugin.ts:232-246
Edge function definitions compile asset bindings and WASM modules directly from entry metadata, mapping binding maps into explicit arrays containing name and file path properties for runtime consumption.
wasm: Array.from(metadata.wasmBindings, ([name, filePath]) => ({
name,
filePath,
})),
assets: Array.from(metadata.assetBindings, ([name, filePath]) => ({
name,
filePath,
})),Sources: packages/next/src/build/webpack/plugins/middleware-plugin.ts:260-267
| Manifest Field / Type | Property / Constant | Description |
|---|---|---|
MiddlewareManifest | version: 3 | Specifies version 3 for the serialized middleware and edge functions descriptor |
EdgeFunctionDefinition | files: string[] | List of compiled output files associated with the Edge function entrypoint |
EdgeFunctionDefinition | matchers: ProxyMatcher[] | Array of route matching objects containing compiled regular expressions and original sources |
EdgeFunctionDefinition | wasm: AssetBinding[] | WebAssembly modules bound to the Edge function execution context |
EdgeFunctionDefinition | assets: AssetBinding[] | Static assets bound to the Edge function |
EdgeFunctionDefinition | env: Record<string, string> | Environment variables injected into the Edge runtime environment |
Sources: packages/next/src/build/webpack/plugins/middleware-plugin.ts:44-64, packages/next/src/build/webpack/plugins/middleware-plugin.ts:192-197
Note
Interception route rewrites are filtered from custom configuration and emitted as a standalone self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST assignment in interception-route-rewrite-manifest.js to supply the Edge runtime with necessary routing context without parsing the entire routes manifest.
Sources: packages/next/src/build/webpack/plugins/middleware-plugin.ts:203-215
Static Analysis Verification and Unsupported APIs
The plugin runs code analysis during JavaScript parser hooks to inspect modules residing within the middleware or edge API Webpack layers (WEBPACK_LAYERS.middleware or WEBPACK_LAYERS.apiEdge). It checks calls and member expressions against EDGE_UNSUPPORTED_NODE_APIS, generating compilation warnings when unsupported Node.js built-ins or process methods are invoked.
function registerUnsupportedApiHooks(
parser: webpack.javascript.JavascriptParser,
compilation: webpack.Compilation
) {
for (const expression of EDGE_UNSUPPORTED_NODE_APIS) {
const warnForUnsupportedApi = (node: any) => {
if (!isInMiddlewareLayer(parser)) {
return
}
compilation.warnings.push(
buildUnsupportedApiError({
compilation,
parser,
apiName: expression,
...node,
})
)
return true
}
parser.hooks.call.for(expression).tap(NAME, warnForUnsupportedApi)
parser.hooks.expression.for(expression).tap(NAME, warnForUnsupportedApi)
parser.hooks.callMemberChain
.for(expression)
.tap(NAME, warnForUnsupportedApi)
parser.hooks.expressionMemberChain
.for(expression)
.tap(NAME, warnForUnsupportedApi)
}Sources: packages/next/src/build/webpack/plugins/middleware-plugin.ts:370-397
Dynamic code evaluation is validated against known safe edge packages and picomatch rules defined in unstable_allowDynamic configurations to decide whether packages utilizing eval are permissible within the Edge Runtime.
function isDynamicCodeEvaluationAllowed(
fileName: string,
middlewareConfig?: ProxyConfig,
rootDir?: string
) {
// Some packages are known to use `eval` but are safe to use in the Edge
// Runtime because the dynamic code will never be executed.
if (
KNOWN_SAFE_DYNAMIC_PACKAGES.some((pkg) =>
fileName.includes(`/node_modules/${pkg}/`.replace(/\//g, path.sep))
)
) {
return true
}
const name = fileName.replace(rootDir ?? '', '')
return picomatch(middlewareConfig?.unstable_allowDynamic ?? [], {
dot: true,
})(name)
}Sources: packages/next/src/build/webpack/plugins/middleware-plugin.ts:330-350
Warning
Accessing process properties other than process.env inside the middleware layer triggers a warning via registerUnsupportedApiHooks, because most process properties are unavailable in the lightweight Edge Runtime.
Sources: packages/next/src/build/webpack/plugins/middleware-plugin.ts:399-413
Related
On This Page
OverviewBuild Manifest Compilation and Asset GenerationInitialization and Compilation Hook RegistrationAsset Collection and Entrypoint ProcessingSerialization and Asset EmissionFlight Manifest and Client Reference ResolutionFlight Manifest Plugin State and StructuresChunk Grouping and Entry NormalizationPages Manifest Mapping and Disk PersistencePages Manifest Plugin MechanicsMulti-Compiler Manifest Merging and Disk PersistenceFont Manifest Optimization and Preloading TablesModule Traversal and Font Asset FilteringManifest Serialization and Asset EmissionLoadable Components and Dynamic Import ManifestsTracking Dynamic Imports and Webpack Chunk BlocksManifest Compilation and Asset GenerationMiddleware Manifest and Edge Function AssetsRoute Matching Regex Generation and Asset BindingStatic Analysis Verification and Unsupported APIsRelated