---
title: "Manifest Plugins"
description: "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 Jav..."
last_updated: "2026-10-12T06:38:56.85659+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/manifest-plugins"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts)
- [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts)
- [packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts)
- [packages/next/src/shared/lib/turbopack/manifest-loader.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/shared/lib/turbopack/manifest-loader.ts)
- [packages/next/src/build/webpack/plugins/next-trace-entrypoints-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-trace-entrypoints-plugin.ts)
- [packages/next/src/build/webpack/plugins/build-manifest-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts)
- [packages/next/src/build/webpack/plugins/middleware-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts)
- [packages/next/src/build/webpack/plugins/react-loadable-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts)
- [packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts)
- [packages/next/src/cli/internal/static-routes-info.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/cli/internal/static-routes-info.ts)
- [packages/next/src/server/route-modules/route-module.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts)
- [packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts)
- [packages/next/src/build/handle-entrypoints.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/handle-entrypoints.ts)
- [packages/next/src/server/dev/turbopack-utils.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/dev/turbopack-utils.ts)
- [packages/next/src/build/utils.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/utils.ts)
- [packages/next/src/build/webpack/plugins/subresource-integrity-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/subresource-integrity-plugin.ts)
- [packages/next/src/build/webpack/plugins/rspack-flight-client-entry-plugin.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/rspack-flight-client-entry-plugin.ts)
- [packages/next/src/build/webpack/plugins/wellknown-errors-plugin/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/wellknown-errors-plugin/index.ts)
</details>

## 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 features[Build Pipeline](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/build-pipeline) such as server component client mapping, font preloading tables, dynamic import chunk tracking, and [edge runtime](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/edge-runtime) route bindings across Webpack and [Turbopack Engine](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/turbopack-engine) compilation pipelines. Sources: [packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts:206-216](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L206-L216), [packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts:55-61](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L55-L61), [packages/next/src/build/webpack/plugins/build-manifest-plugin.ts:102-128](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts#L102-L128)

## 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. 

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts#L102-L128)

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`.

```typescript
  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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts#L266-L279)

---

### 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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts#L88-L98)

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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts#L36-L45)

---

### 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin.ts#L229-L261), [packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts:11-58](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts#L11-L58)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts#L25-L34)

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/build-manifest-plugin-utils.ts#L25-L34)

## Flight Manifest and Client Reference Resolution

### Flight Manifest Plugin State and Structures

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L46-L52)

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L115-L141)

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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L54-L84)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L141-L147)

### 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. 

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L161-L184)

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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L118-L150)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts#L299-L315)

## 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`.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts#L181-L191)

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`.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts#L46-L73)

| 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts#L24-L44)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts#L77-L83)

### 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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts#L13-L16)

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`.

```typescript
// 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts#L101-L122)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/pages-manifest-plugin.ts#L101-L122)

## 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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L55-L76)

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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L78-L155)

| 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L26-L39)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L107-L116)

### 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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L7-L16)

```typescript
  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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L157-L173)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/next-font-manifest-plugin.ts#L50-L54)

## 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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts#L239-L251)

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.

```typescript
// 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts#L83-L88)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts#L79-L82)

### 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.

```typescript
// 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts#L93-L106)

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/`.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts#L125-L141)

| 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts#L202-L236)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/react-loadable-plugin.ts#L147-L149)

## 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](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router) paths or page paths and invokes `getNamedMiddlewareRegex` to build precise route matching expressions.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L232-L246)

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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L260-L267)

| 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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L44-L64), [packages/next/src/build/webpack/plugins/middleware-plugin.ts:192-197](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L192-L197)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L203-L215)

### 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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L370-L397)

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.

```typescript
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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L330-L350)

> [!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](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/webpack/plugins/middleware-plugin.ts#L399-L413)

## Related

- [Webpack Configuration](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/webpack-configuration)
- [Route Discovery](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/route-discovery)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
