---
title: "Request Lifecycle"
description: "The request lifecycle orchestrates how HTTP requests are received, parsed, routed, and fulfilled across both Node.js and Edge Runtime environments. It manages request metadata propagation via Async..."
last_updated: "2026-10-12T06:38:56.881153+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/request-lifecycle"
---

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

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

- [packages/next/src/server/base-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/base-server.ts)
- [packages/next/src/server/next-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/next-server.ts)
- [packages/next-routing/src/resolve-routes.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next-routing/src/resolve-routes.ts)
- [packages/next/src/server/lib/router-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-server.ts)
- [packages/next/src/build/templates/app-route.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts)
- [packages/next/src/build/templates/app-page-runtime.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts)
- [packages/next/src/server/lib/router-utils/resolve-routes.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/resolve-routes.ts)
- [packages/next/src/server/dev/next-dev-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/dev/next-dev-server.ts)
- [packages/next/src/server/web/adapter.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/adapter.ts)
- [packages/next/src/build/templates/edge-ssr-app.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/edge-ssr-app.ts)
- [packages/next/src/build/templates/pages-api.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts)
- [packages/next/src/server/route-modules/app-route/module.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-route/module.ts)
- [packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts)
- [packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts)
- [packages/next/src/server/request-meta.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts)
- [packages/next/src/server/send-payload.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts)
- [packages/next/src/build/duration-to-string.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/duration-to-string.ts)
- [packages/next/src/server/config.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/config.ts)
</details>

## Overview

The request lifecycle orchestrates how HTTP requests are received, parsed, routed, and fulfilled across both Node.js and [Edge Runtime](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/edge-runtime) environments. It manages request metadata propagation via [Async Storage](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/async-storage), matches incoming paths against custom routes, headers, and redirects, and normalizes bundle paths for App and Pages directories. The pipeline controls the execution of Server Components, [Route Handlers](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/route-handlers), and legacy Pages APIs while interfacing with the [Incremental Cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/incremental-cache) to dispatch optimized payloads and responses.

Sources: [packages/next/src/server/base-server.ts:1071-1080](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/base-server.ts#L1071-L1080), [packages/next/src/server/web/adapter.ts:127-145](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/adapter.ts#L127-L145), [packages/next/src/server/request-meta.ts:51-175](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L51-L175), [packages/next-routing/src/resolve-routes.ts:557-600](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next-routing/src/resolve-routes.ts#L557-L600), [packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts:7-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts#L7-L15), [packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts:7-21](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts#L7-L21), [packages/next/src/build/templates/app-page-runtime.ts:245-280](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L245-L280), [packages/next/src/build/templates/pages-api.ts:47-80](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L47-L80), [packages/next/src/server/send-payload.ts:35-50](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L35-L50)

## Edge and Server Request Metadata

### Request Metadata Operations

Per-request metadata tracking in Next.js relies on a shared internal symbol `NEXT_REQUEST_META` (`Symbol.for('NextInternalRequestMeta')`) attached directly to incoming message objects (`NextIncomingMessage`), bridging Node.js HTTP requests, base HTTP wrappers, and Web API request types.

Sources: [packages/next/src/server/request-meta.ts:19-27](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L19-L27)

During request execution, the invocation path passes through specific middleware handling routines. In `handleCatchallMiddlewareRequest()`, the server checks whether the request has an active middleware invocation flag. When middleware is absent or routing matchers evaluate to false, `handleFinished()` is invoked. Inside `handleFinished()`, the call chain invokes `addRequestMeta(req, 'middlewareInvoke', true)` to record the completed state, which in turn calls `getRequestMeta()` to read the existing metadata dictionary before setting the key. The full call sequence is `handleCatchallMiddlewareRequest` → `handleFinished` → `addRequestMeta` → `getRequestMeta`.

Sources: [packages/next/src/server/next-server.ts:1943-2053](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/next-server.ts#L1943-L2053), [packages/next/src/server/request-meta.ts:421-429](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L421-L429), [packages/next/src/server/request-meta.ts:393-399](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L393-L399)

```mermaid
sequenceDiagram
    participant NextNodeServer as next-server.ts
    participant RequestMeta as request-meta.ts
    NextNodeServer->>NextNodeServer: handleCatchallMiddlewareRequest()
    NextNodeServer->>NextNodeServer: handleFinished()
    NextNodeServer->>RequestMeta: addRequestMeta()
    RequestMeta->>RequestMeta: getRequestMeta()
```

Sources: [packages/next/src/server/next-server.ts:1943-2053](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/next-server.ts#L1943-L2053), [packages/next/src/server/request-meta.ts:421-429](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L421-L429), [packages/next/src/server/request-meta.ts:393-399](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L393-L399)

### Metadata Fields and Structure

The `RequestMeta` interface maintains state regarding routing decisions, incremental caching layers, locale negotiation, and development diagnostics.

| Field | Type | Purpose |
| :--- | :--- | :--- |
| `initQuery` | `ParsedUrlQuery` | The initial query string parameters used to make the request. |
| `initURL` | `string` | The full request URL string captured at entry. |
| `initProtocol` | `string` | The protocol scheme (`http` or `https`) associated with the request. |
| `clonableBody` | `CloneableBody` | Multi-readable request body stream wrapper. |
| `isLocaleDomain` | `boolean` | True when the request matches a configured locale domain. |
| `didStripLocale` | `boolean` | True when locale information was stripped from the URL pathname. |
| `rewrittenPathname` | `string` | The target pathname if the request URL underwent a rewrite. |
| `middlewareCookie` | `string[]` | Cookies added by middleware to be appended to the response. |
| `match` | `RouteMatch` | The resolved route match object for the current pathname. |
| `incrementalCache` | `IncrementalCache` | The incremental cache provider instance assigned to the request. |
| `serverComponentsHmrCache` | `ServerComponentsHmrCache` | Development-only HMR cache for server components. |
| `hmrRefreshHash` | `string` | Hash of the latest server component change used in `"use cache"` keys. |
| `segmentPrefetchRSCRequest` | `string` | Target segment path for prefetch RSC requests. |
| `isPrefetchRSCRequest` | `true` | True when the request fetches prefetch flight data. |
| `isRSCRequest` | `true` | True when the request fetches React Server Component flight data. |
| `cacheBustingSearchParam` | `string` | Search param hash verifying custom headers to prevent cache poisoning on CDNs. |
| `isNextDataReq` | `true` | True when requesting `/_next/data` via the Pages Router. |
| `postponed` | `string` | Postponed PPR state string used for resumption rendering. |
| `actionBody` | `Buffer` | Action payload extracted from server action requests. |
| `onCacheEntry` | `OnCacheEntryHandler` | Deprecated callback invoked on response cache hits or generations. |
| `onCacheEntryV2` | `OnCacheEntryHandler` | Callback invoked when a response cache entry is generated or retrieved. |
| `notFoundRevalidate` | `number \| false` | Revalidation interval prior to rendering a 404 page for `notFound: true`. |
| `developmentNotFoundSourcePage` | `string` | Original source page returning a 404 during development. |
| `invokePath` | `string` | Internal routing invocation path. |
| `invokeOutput` | `string` | Specific page output target for matching. |
| `invokeStatus` | `number` | Status code passed during internal routing invocation. |
| `invokeError` | `Error` | Routing error object associated with an invocation. |
| `invokeQuery` | `Record<string, ...>` | Parsed query parameters for internal routing invocations. |
| `middlewareInvoke` | `boolean` | Flag indicating whether the request is a middleware invocation. |
| `renderFallbackShell` | `boolean` | True if the server should render the fallback shell. |
| `resolvedRouteParamKeys` | `Set<string>` | Route parameter keys explicitly resolved from partial query parameters. |
| `customErrorRender` | `true` | True when rendering the custom application error page. |
| `bubbleNoFallback` | `true` | Bubble `NoFallbackError` to the caller when a 404 occurs. |
| `localeInferredFromDefault` | `true` | True when the locale was inferred from the default configuration. |
| `locale` | `string` | The active locale set or inferred for the request. |
| `defaultLocale` | `string` | The default locale configured for the application. |
| `relativeProjectDir` | `string` | Relative path of the project directory from the root. |
| `distDir` | `string` | Build output distribution directory. |
| `hostname` | `string` | Hostname used by route handlers for absolute URL construction. |
| `revalidate` | `RevalidateFn` | Internal revalidation function bypassing network calls. |
| `render404` | `Function` | Callback rendering the Pages Router 404 page. |
| `query` | `ParsedUrlQuery` | Resolved query parameters after route matching. |
| `params` | `ParsedUrlQuery` | Resolved dynamic route parameters after route matching. |
| `PagesErrorDebug` | `PagesDevOverlayBridgeType` | Error overlay component bridge for the Pages Router in development. |
| `minimalMode` | `boolean` | True when running the server in minimal resource mode. |
| `stagedFallbackParams` | `OpaqueFallbackRouteParams` | Route params deferred until after the static stage in PPR. |
| `fallbackRouteParams` | `OpaqueFallbackRouteParams` | Route params substituted with opaque placeholders during shell debugging. |
| `devParamMatchingRejected` | `boolean` | Development flag indicating parameter matching rejection. |
| `devNotFoundParams` | `readonly string[]` | Closed parameters including allowed values in development. |
| `devRequestTimingStart` | `bigint` | Request start timestamp in nanoseconds via `process.hrtime.bigint()`. |
| `devRequestTimingMiddlewareStart` | `bigint` | Middleware execution start timestamp in development. |
| `devRequestTimingMiddlewareEnd` | `bigint` | Middleware execution completion timestamp in development. |
| `devRequestTimingInternalsEnd` | `bigint` | Internal processing completion timestamp in development. |
| `devGenerateStaticParamsDuration` | `bigint` | Duration of `getStaticPaths` or `generateStaticParams` execution. |
| `devServerActionLog` | `object` | Server action execution logs captured in development. |

Sources: [packages/next/src/server/request-meta.ts:51-375](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L51-L375)

> [!NOTE]
> The `NEXT_REQUEST_META` symbol allows mutable request attributes to persist across framework boundaries, including Node.js `IncomingMessage`, custom `BaseNextRequest` wrappers, and Web `NextRequest` abstractions, preventing metadata loss during internal request forwarding and middleware runs.
Sources: [packages/next/src/server/request-meta.ts:18-27](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L18-L27)

## Route Resolution and Header Rewriting

### Path Matching and Internationalization

Incoming HTTP requests undergo initial route resolution through `resolveRoutes`, which evaluates request URLs, normalizes `_next/data` requests when `shouldNormalizeNextData` is enabled, and applies internationalization (i18n) locale detection. When i18n is configured and the request does not target `_next` or `api` paths, the system inspects cookies, accept-language headers, pathnames, and domain configuration.

```typescript
// Handle i18n locale detection and redirects
if (i18n && !isDataUrl) {
  const pathname = currentUrl.pathname.startsWith(basePath)
    ? currentUrl.pathname.slice(basePath.length) || '/'
    : currentUrl.pathname

  // Skip locale handling for _next and api routes
  if (!pathname.startsWith('/_next/') && !pathname.startsWith('/api/')) {
    const hostname = currentUrl.hostname
    const cookieHeader = currentRequestHeaders.get('cookie') || undefined
    const acceptLanguageHeader =
      currentRequestHeaders.get('accept-language') || undefined

    // Detect locale from path first
    const pathLocaleResult = normalizeLocalePath(pathname, i18n.locales)
    const localeInPath = !!pathLocaleResult.detectedLocale

    // Detect domain locale
    const domainLocale = detectDomainLocale(i18n.domains, hostname)
    const defaultLocale = domainLocale?.defaultLocale || i18n.defaultLocale

    // Determine target locale if locale detection is enabled
    let targetLocale = pathLocaleResult.detectedLocale || defaultLocale

    // Match Next.js behavior: preferred-locale auto-detection redirects only
    // on index requests, not on arbitrary non-locale pathnames.
    const shouldDetectPreferredLocale =
      i18n.localeDetection !== false &&
      !localeInPath &&
      pathLocaleResult.pathname === '/'

    if (shouldDetectPreferredLocale) {
      const detectedResult = detectLocale({
        pathname,
        hostname,
        cookieHeader,
        acceptLanguageHeader,
        i18n,
      })

      targetLocale = detectedResult.locale
```
Sources: [packages/next-routing/src/resolve-routes.ts:602-643](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next-routing/src/resolve-routes.ts#L602-L643)

If preferred-locale auto-detection triggers on root index requests without an explicit path locale, the request redirects to the detected locale or domain. Otherwise, the locale is prefixed internally for route resolution without triggering an HTTP redirect.

> [!TIP]
> Preferred-locale auto-detection is restricted strictly to root index requests (`pathLocaleResult.pathname === '/'`) when no explicit locale exists in the path, preventing unexpected redirects on nested application routes.
Sources: [packages/next-routing/src/resolve-routes.ts:628-632](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next-routing/src/resolve-routes.ts#L628-L632)

### Middleware Execution and Header Overrides

After processing `beforeMiddleware` rules, the routing engine evaluates whether the request matches any middleware matchers. If matched, middleware is invoked, and any resulting headers, redirects, or rewrites are applied to the request and response objects.

```typescript
const middlewareHeaders = toNodeOutgoingHttpHeaders(
  middlewareRes.headers
) as Record<string, string | string[] | undefined>

debug('middleware res', middlewareRes.status, middlewareHeaders)

if (middlewareHeaders['x-middleware-override-headers']) {
  const overriddenHeaders: Set<string> = new Set()
  let overrideHeaders: string | string[] =
    middlewareHeaders['x-middleware-override-headers']

  if (typeof overrideHeaders === 'string') {
    overrideHeaders = overrideHeaders.split(',')
  }

  for (const key of overrideHeaders) {
    overriddenHeaders.add(key.trim())
  }
  delete middlewareHeaders['x-middleware-override-headers']

  // Delete headers.
  for (const key of Object.keys(req.headers)) {
    if (!overriddenHeaders.has(key)) {
      delete req.headers[key]
    }
  }

  // Update or add headers.
  for (const key of overriddenHeaders.keys()) {
    const valueKey = 'x-middleware-request-' + key
    const newValue = middlewareHeaders[valueKey]
    const oldValue = req.headers[key]

    if (oldValue !== newValue) {
      req.headers[key] = newValue === null ? undefined : newValue
    }
    delete middlewareHeaders[valueKey]
  }
}
```
Sources: [packages/next/src/server/lib/router-utils/resolve-routes.ts:662-700](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/resolve-routes.ts#L662-L700)

When middleware returns `x-middleware-override-headers`, the server parses the specified header names, strips unlisted incoming request headers, and applies new values prefixed with `x-middleware-request-`. Headers explicitly blocked by `ipcForbiddenHeaders` or control keys such as `content-length`, `x-middleware-rewrite`, `x-middleware-redirect`, and `x-middleware-refresh` are filtered out or handled specially.

> [!CAUTION]
> The `x-middleware-set-cookie` header is assigned directly to request headers (`req.headers['x-middleware-set-cookie'] = value`) rather than copied to response headers, ensuring cookies passed from middleware persist for downstream request processing.
Sources: [packages/next/src/server/lib/router-utils/resolve-routes.ts:725-730](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-utils/resolve-routes.ts#L725-L730)

### Dynamic Routes and Condition Evaluation

Once file system checks and `beforeFiles` rewrites complete, dynamic routes are scanned against pathnames and query conditions using `has` and `missing` parameters.

```typescript
for (const route of routes.dynamicRoutes) {
  const match = matchDynamicRoute(currentUrl.pathname, route, caseSensitive)

  if (!match.matched) {
    continue
  }

  const hasResult = checkHasConditions(
    route.has,
    currentUrl,
    currentRequestHeaders
  )
  const missingMatched = checkMissingConditions(
    route.missing,
    currentUrl,
    currentRequestHeaders
  )

  if (!hasResult.matched || !missingMatched) {
    continue
  }
```
Sources: [packages/next-routing/src/resolve-routes.ts:881-901](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next-routing/src/resolve-routes.ts#L881-L901)

If a dynamic route matches both pattern criteria and validation conditions, its destination is resolved via query merging and matched headers from `routes.onMatch` are appended to the response context.

## Path Normalization and Manifest Mapping

### App Router Normalizers

The [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router) utilizes specialized normalizers to transform route strings and filenames into canonical bundle paths. The `AppBundlePathNormalizer` extends `PrefixingNormalizer` with an `'app'` prefix, applying `normalizePagePath` to convert pages into their normalized pathname representations.
Sources: [packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts:7-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts#L7-L15)

In development mode, `DevAppBundlePathNormalizer` orchestrates a composite normalizer chain via `Normalizers`. It combines the incoming page normalizer and an `AppBundlePathNormalizer`, conditionally prepending an `UnderscoreNormalizer` when Turbopack is active to handle `%5F` to `_` replacements.
Sources: [packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts:17-31](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts#L17-L31)

> [!NOTE]
> The `%5F` to `_` underscore replacement in bundle paths is restricted strictly to Turbopack compilation runs within `DevAppBundlePathNormalizer`.
Sources: [packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts:26-29](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts#L26-L29)

### Pages Router Normalizers

For the legacy Pages Router, `PagesBundlePathNormalizer` wraps `normalizePagePath` to strip trailing `/index` segments from bundle paths before passing them through a `PrefixingNormalizer` initialized with the `'pages'` prefix.
Sources: [packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts:7-16](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts#L7-L16)

The corresponding development variant, `DevPagesBundlePathNormalizer`, composes the pages-specific normalizer with a runtime page normalizer to translate filenames into target route paths during on-demand compilation.
Sources: [packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts:23-31](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts#L23-L31)

| Normalizer Class | Base Class / Wrapper | Prefix / Target | Core Operation |
| :--- | :--- | :--- | :--- |
| `AppBundlePathNormalizer` | `PrefixingNormalizer` | `'app'` | Normalizes page paths via `normalizePagePath` and prefixes with `'app'` |
| `DevAppBundlePathNormalizer` | `Normalizers` | `'app'` (+ optional `UnderscoreNormalizer`) | Combines page normalizer, app normalizer, and conditional Turbopack underscore normalization |
| `PagesBundlePathNormalizer` | `Normalizers` | `'pages'` | Strips trailing `/index` via `normalizePagePath` and prefixes with `'pages'` |
| `DevPagesBundlePathNormalizer` | `Normalizers` | `'pages'` | Chains pages file normalizer with `PagesBundlePathNormalizer` |

Sources: [packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts:7-31](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/app/app-bundle-path-normalizer.ts#L7-L31), [packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts:7-31](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/normalizers/built/pages/pages-bundle-path-normalizer.ts#L7-L31)

## App Page Runtime Execution Pipeline

### Runtime Template Invocation and Handler Initialization

The App Page runtime execution pipeline begins when incoming requests are processed by generated runtime templates such as `packages/next/src/build/templates/app-page-runtime.ts` and Edge SSR templates like `packages/next/src/build/templates/edge-ssr-app.ts`. The main `handler` function extracts request metadata, normalizes source paths via `normalizeAppPath`, and parses dynamic route segment parameter names from the route pattern.
Sources: [packages/next/src/build/templates/app-page-runtime.ts:206-222](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L206-L222)

```typescript
async function handler(
  req: IncomingMessage,
  res: ServerResponse,
  ctx: {
    waitUntil?: (prom: Promise<void>) => void
    requestMeta?: RequestMeta
  }
) {
  if (ctx.requestMeta) {
    setRequestMeta(req, ctx.requestMeta)
  }
```
Sources: [packages/next/src/build/templates/app-page-runtime.ts:215-225](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L215-L225)

Requests are prepared via `routeModule.prepare`, evaluating multi-zone draft mode configuration and returning a comprehensive context object containing build manifests, router contexts, query parameters, and configuration properties.
Sources: [packages/next/src/build/templates/app-page-runtime.ts:245-280](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L245-L280)

> [!WARNING]
> Failing to capture `req.url` into `httpTarget` before `routeModule.prepare` runs will cause subsequent CDN URL rewrites to corrupt trace span attributes.
Sources: [packages/next/src/build/templates/app-page-runtime.ts:236-241](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L236-L241)

### PPR Resumption and Server Actions Handling

When Partial Prerendering (PPR) is enabled via `nextConfig.cacheComponents`, incoming requests accompanied by postponed resume state headers undergo body size and limit validations. If `NEXT_RESUME_STATE_LENGTH_HEADER` is present alongside a server action request, the runtime reads the request body with a combined size budget comprising the state length and action body limit.
Sources: [packages/next/src/build/templates/app-page-runtime.ts:349-390](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L349-L390)

```typescript
const resumeStateLengthHeader = req.headers[NEXT_RESUME_STATE_LENGTH_HEADER]
if (
  !getRequestMeta(req, 'postponed') &&
  isMinimalMode &&
  couldSupportPPR &&
  isPossibleServerAction &&
  resumeStateLengthHeader &&
  typeof resumeStateLengthHeader === 'string'
) {
  const stateLength = parseInt(resumeStateLengthHeader, 10)
  const { maxPostponedStateSize, maxPostponedStateSizeBytes } =
    getMaxPostponedStateSize(nextConfig.experimental.maxPostponedStateSize)

  if (!isNaN(stateLength) && stateLength > 0) {
    if (stateLength > maxPostponedStateSizeBytes) {
      res.statusCode = 413
      res.end(getPostponedStateExceededErrorMessage(maxPostponedStateSize))
      ctx.waitUntil?.(Promise.resolve())
      return null
    }
```
Sources: [packages/next/src/build/templates/app-page-runtime.ts:353-372](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L353-L372)

The raw request buffer is subdivided into the postponed state string—stored in request metadata under `'postponed'`—and the remaining action body payload destined for the server action handler.
Sources: [packages/next/src/build/templates/app-page-runtime.ts:403-411](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L403-L411)

### Route Invocation and Edge SSR Adapters

Route modules execute rendering operations by wrapping Node request and response objects into `NodeNextRequest` and `NodeNextResponse` adapters, dispatching either `routeModule.prerender` or `routeModule.render` depending on the active render operation.
Sources: [packages/next/src/build/templates/app-page-runtime.ts:777-789](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L777-L789)

```typescript
const invokeRouteModule = async (
  span: Span | undefined,
  context: AppPageRouteHandlerContext,
  renderOperation: AppPageRenderOperation
) => {
  const nextReq = new NodeNextRequest(req)
  const nextRes = new NodeNextResponse(res)

  const result =
    renderOperation === 'prerender'
      ? routeModule.prerender(nextReq, nextRes, context)
      : routeModule.render(nextReq, nextRes, context)

  return result.finally(() => {
```
Sources: [packages/next/src/build/templates/app-page-runtime.ts:777-790](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L777-L790)

In Edge SSR runtime templates, `renderResultToResponse` converts render outcomes into standard Web `Response` instances, setting appropriate `x-edge-runtime` headers, mapping fetch metrics, and piping dynamic readable streams via `TransformStream`.
Sources: [packages/next/src/build/templates/edge-ssr-app.ts:250-328](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/edge-ssr-app.ts#L250-L328)

| Operation Context | Target Parameter / Header | Default / Fallback Value | Purpose |
| :--- | :--- | :--- | :--- |
| Action Body Limit | `nextConfig.experimental.serverActions?.bodySizeLimit` | `'1 MB'` (1024 * 1024 bytes) | Bounded payload size check for server action requests |
| Postponed State Size | `nextConfig.experimental.maxPostponedStateSize` | Platform default limits | Validates incoming PPR postponed state length against configuration |
| Response Content-Type | `result.contentType` | `'text/html; charset=utf-8'` | Establishes mime type for Edge SSR responses |
| Edge Runtime Header | `'x-edge-runtime'` | `'1'` | Identifies responses originating from the V8 edge sandbox |

Sources: [packages/next/src/build/templates/app-page-runtime.ts:378-387](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-page-runtime.ts#L378-L387), [packages/next/src/build/templates/edge-ssr-app.ts:272-274](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/edge-ssr-app.ts#L272-L274)

## Pages API Template Request Processing

### Request Preparation and Route Module Initialization

The legacy Pages API handler initializes by accepting incoming `IncomingMessage` and `ServerResponse` objects alongside a context object containing optional `waitUntil` and `requestMeta` properties. If `requestMeta` is provided, it is attached to the request, and development timing metrics are recorded if `routeModule.isDev` is active. For Turbopack environments, dynamic route page paths ending in `/index` are normalized to ensure correct path matching.
Sources: [packages/next/src/build/templates/pages-api.ts:47-68](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L47-L68)

```typescript
const prepareResult = await routeModule.prepare(req, res, { srcPage })

if (!prepareResult) {
  res.statusCode = 400
  res.end('Bad Request')
  ctx.waitUntil?.(Promise.resolve())
  return
}
```
Sources: [packages/next/src/build/templates/pages-api.ts:70-77](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L70-L77)

> [!WARNING]
> If `routeModule.prepare` returns a falsy result, the response status code is immediately set to `400` with a `"Bad Request"` body, short-circuiting execution before any userland route module rendering occurs.
Sources: [packages/next/src/build/templates/pages-api.ts:72-77](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L72-L77)

### Tracing Context and Route Execution

When preparation succeeds, the request method and active OpenTelemetry tracer are retrieved to establish distributed tracing spans. Depending on whether the server wraps the request, the handler either uses an existing active span or initiates a new `BaseServerSpan.handleRequest` trace with server span kinds and route attributes.
Sources: [packages/next/src/build/templates/pages-api.ts:79-92](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L79-L92), [packages/next/src/build/templates/pages-api.ts:172-196](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L172-L196)

```typescript
const invokeRouteModule = async (span?: Span) =>
  routeModule
    .render(req, res, {
      query: {
        ...query,
        ...params,
      },
      params,
      allowedRevalidateHeaderKeys: process.env
        .__NEXT_ALLOWED_REVALIDATE_HEADERS as any as string[],
      multiZoneDraftMode: Boolean(process.env.__NEXT_MULTI_ZONE_DRAFT_MODE),
      trustHostHeader: process.env
        .__NEXT_TRUST_HOST_HEADER as any as boolean,
      // TODO: get this from from runtime env so manifest
      // doesn't need to load
      previewProps,
      propagateError: false,
      dev: routeModule.isDev,
      page: 'VAR_DEFINITION_PAGE',

      internalRevalidate: routerServerContext?.revalidate,

      onError: (...args: Parameters<InstrumentationOnRequestError>) =>
        onRequestError(req, ...args),
    })
```
Sources: [packages/next/src/build/templates/pages-api.ts:93-117](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L93-L117)

Spans are finalized via `.finally()` hooks that inspect `res.statusCode`. Status codes of `500` or greater trigger `SpanStatusCode.ERROR` along with `error.type` attributes. Root span attributes are verified to match `BaseServerSpan.handleRequest` before updating trace span names with HTTP methods and matched route paths.
Sources: [packages/next/src/build/templates/pages-api.ts:118-170](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L118-L170)

### Error Boundary Traps and Response Delivery

Uncaught exceptions thrown during route module invocation are intercepted by a catch block. In development mode, errors are re-thrown directly to trigger the error overlay. In production environments, an internal server error is dispatched using `sendError`, serving a `500` status response.
Sources: [packages/next/src/build/templates/pages-api.ts:197-205](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L197-L205)

```typescript
} finally {
  // We don't allow any waitUntil work in pages API routes currently
  // so if callback is present return with resolved promise since no
  // pending work
  ctx.waitUntil?.(Promise.resolve())
}
```
Sources: [packages/next/src/build/templates/pages-api.ts:205-210](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L205-L210)

> [!NOTE]
> Pages API routes do not currently permit asynchronous background work via `waitUntil`, so the `finally` block immediately resolves any pending `waitUntil` promise callbacks.
Sources: [packages/next/src/build/templates/pages-api.ts:206-210](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L206-L210)

## Response Payload Dispatch and Caching

### ETag Validation and Caching Headers

The payload dispatch pipeline verifies response state and sets caching and metadata headers before outputting content. If a response has already been sent, execution returns immediately. When `poweredByHeader` is enabled and the response content type matches the HTML content type header, the `X-Powered-By: Next.js` header is assigned.
Sources: [packages/next/src/server/send-payload.ts:35-56](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L35-L56)

```typescript
// If cache control is already set on the response we don't
// override it to allow users to customize it via next.config
if (cacheControl && !res.getHeader('Cache-Control')) {
  res.setHeader('Cache-Control', getCacheControlHeader(cacheControl))
}

const payload = result.isDynamic ? null : result.toUnchunkedString()

if (generateEtags && payload !== null) {
  const etag = generateETag(payload)
  if (sendEtagResponse(req, res, etag)) {
    return
  }
}
```
Sources: [packages/next/src/server/send-payload.ts:58-71](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L58-L71)

Cache control headers are populated using `getCacheControlHeader` if not already present, preserving user customizations set via `next.config`. For unchunked static payloads, ETags are generated and evaluated via `sendEtagResponse`. If the request headers match the generated ETag using `fresh`, status `304` is assigned, the connection is ended, and dispatch terminates early.
Sources: [packages/next/src/server/send-payload.ts:11-33](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L11-L33), [packages/next/src/server/send-payload.ts:58-71](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L58-L71)

> [!WARNING]
> When generating a `304` Not Modified response, the server sets required validation headers such as `ETag` per RFC 7232 before ending the response without a body.
Sources: [packages/next/src/server/send-payload.ts:16-24](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L16-L24)

### Payload Streaming and Node Response Piping

```typescript
if (!res.getHeader('Content-Type') && result.contentType) {
  res.setHeader('Content-Type', result.contentType)
}

if (payload) {
  res.setHeader('Content-Length', Buffer.byteLength(payload))
}

if (req.method === 'HEAD') {
  res.end(null)
  return
}

if (payload !== null) {
  res.end(payload)
  return
}

// Pipe the render result to the response after we get a writer for it.
await result.pipeToNodeResponse(res)
```
Sources: [packages/next/src/server/send-payload.ts:73-92](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L73-L92)

Content type and content length headers are registered for non-empty payloads. If the incoming method is `HEAD`, the body is omitted and the response is terminated. Unchunked string payloads are dispatched directly via `res.end(payload)`, whereas dynamic streaming results are piped to the underlying Node response using `result.pipeToNodeResponse(res)`.
Sources: [packages/next/src/server/send-payload.ts:73-92](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/send-payload.ts#L73-L92)

## Related

- [Server Architecture](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/server-architecture)
- [App Rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/app-rendering)
- [Pages Rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/pages-rendering)


## Sitemap

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