---
title: "Route Handlers"
description: "Route Handlers provide a unified mechanism for executing custom server-side request handlers and API endpoints within Next.js, bridging both the App Router (via AppRouteRouteModule) and the Pages A..."
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/route-handlers"
---

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

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

- [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/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/app-render/action-handler.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/app-render/action-handler.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/pages/pages-handler.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/pages/pages-handler.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/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/src/server/web/edge-route-module-wrapper.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/edge-route-module-wrapper.ts)
- [packages/next/src/server/api-utils/node/api-resolver.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/node/api-resolver.ts)
- [packages/next/src/build/templates/edge-app-route.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/edge-app-route.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/lib/router-server.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/router-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.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/edge-ssr.ts)
- [packages/next/src/server/route-modules/pages-api/module.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/pages-api/module.ts)
- [packages/next/src/server/api-utils/index.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/index.ts)
- [packages/next/src/server/route-modules/app-page/module.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-page/module.ts)
- [packages/next/src/server/lib/patch-set-header.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/patch-set-header.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/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/server/web/get-edge-preview-props.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/get-edge-preview-props.ts)
</details>

## Overview

Route Handlers provide a unified mechanism for executing custom server-side request handlers and API endpoints within Next.js, bridging both the [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router) (via `AppRouteRouteModule`) and the Pages API router (via `PagesAPIRouteModule`). By building upon the foundational `RouteModule` class, route handlers establish a consistent architecture for environment initialization, manifest loading, error reporting, and request preparation across diverse runtimes. 

Sources: [packages/next/src/server/route-modules/app-route/module.ts:10-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-route/module.ts#L10-L15), [packages/next/src/server/route-modules/pages-api/module.ts:8-10](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/pages-api/module.ts#L8-L10), [packages/next/src/server/route-modules/route-module.ts:119-164](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L119-L164)

## Route Module Base Architecture

### Execution Walkthrough and Sequence

The abstract `RouteModule` class serves as the core foundation for all Next.js route handlers, providing a shared environment initialization, manifest loading, error reporting, and request preparation pipeline. During initialization, the constructor accepts `RouteModuleOptions` containing the `definition`, `userland` module, `distDir`, and `relativeProjectDir`, setting up the canonical `cacheOwner` via `getResponseCacheOwner(definition)` and detecting the development runtime mode through `process.env.__NEXT_DEV_SERVER`.

Sources: [packages/next/src/server/route-modules/route-module.ts:89-164](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L89-L164)

When a request arrives at a route, the execution flows through the entry point template handler and initializes through the base class. The invocation sequence executes in the following call order: `handler` → `prepare` → `prepareImpl` → `patchSetHeaderWithCookieSupport` → `getRequestMeta`.

1. `handler`: The template entry function receives the incoming request and response objects, attaches request metadata if provided, and invokes `routeModule.prepare()`.
Sources: [packages/next/src/build/templates/app-route.ts:106-137](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts#L106-L137)

2. `prepare`: Traces the preparation span using the tracer utility and delegates the execution to `prepareImpl()`.
Sources: [packages/next/src/server/route-modules/route-module.ts:635-648](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L635-L648)

3. `prepareImpl`: Evaluates runtime environment conditions, patches headers, resolves absolute project directories, loads route manifests, normalizes URLs, handles localization, and processes rewrites and dynamic route parameters.
Sources: [packages/next/src/server/route-modules/route-module.ts:650-735](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L650-L735)

4. `patchSetHeaderWithCookieSupport`: Wraps the response `setHeader` method to intercept `set-cookie` headers, ensuring middleware cookies are properly merged rather than overridden by downstream handlers.
Sources: [packages/next/src/server/lib/patch-set-header.ts:18-65](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/patch-set-header.ts#L18-L65), [packages/next/src/server/route-modules/route-module.ts:707-710](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L707-L710)

5. `getRequestMeta`: Retrieves internal request metadata properties such as `middlewareCookie`, `relativeProjectDir`, and `distDir` from the request symbol storage.
Sources: [packages/next/src/server/request-meta.ts:385-399](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L385-L399), [packages/next/src/server/lib/patch-set-header.ts:43-43](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/patch-set-header.ts#L43-L43)

```mermaid
sequenceDiagram
    participant AppRoute as app-route.ts
    participant RouteModule as route-module.ts
    participant PatchHeader as patch-set-header.ts
    participant RequestMeta as request-meta.ts

    AppRoute->>RouteModule: handler() calls prepare()
    RouteModule->>RouteModule: prepare() traces and calls prepareImpl()
    RouteModule->>PatchHeader: prepareImpl() calls patchSetHeaderWithCookieSupport(req, res)
    PatchHeader->>RequestMeta: patchSetHeaderWithCookieSupport checks getRequestMeta(req, 'middlewareCookie')
```

Sources: [packages/next/src/build/templates/app-route.ts:134-137](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts#L134-L137), [packages/next/src/server/route-modules/route-module.ts:635-648](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L635-L648), [packages/next/src/server/route-modules/route-module.ts:707-710](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L707-L710), [packages/next/src/server/lib/patch-set-header.ts:18-43](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/patch-set-header.ts#L18-L43), [packages/next/src/server/request-meta.ts:385-399](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/request-meta.ts#L385-L399)

> [!NOTE]
> During header patching, `patchSetHeaderWithCookieSupport` intercepts calls to `res.setHeader`. When the header name matches `set-cookie`, it pulls any pending cookies set by middleware via `getRequestMeta(req, 'middlewareCookie')` and combines them with unique entries to prevent downstream overwrites.

Sources: [packages/next/src/server/lib/patch-set-header.ts:32-62](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/lib/patch-set-header.ts#L32-L62)

## App Route Module Execution

### Dynamic Dispatch and HTTP Method Resolution

Incoming requests to an app route undergo preparation via `prepareExecution`, which ensures the userland module is fully loaded through `this.ensureUserland()`. In Turbopack development mode, the module resolver fetches the live userland module on each request via `this._getUserland?.()` to reflect immediate HMR updates, falling back to cached modules if top-level await yields a pending promise. The target HTTP method is inspected against the loaded userland module using `this.resolveHandlerFromUserland(req.method, liveUserland)` or `this.resolveHandler(req.method)`.

Sources: [packages/next/src/server/route-modules/app-route/module.ts:851-874](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-route/module.ts#L851-L874)

```mermaid
sequenceDiagram
    participant NextReq as NextRequest
    participant AppRouteMod as AppRouteRouteModule
    participant Userland as Userland Module

    NextReq->>AppRouteMod: prepareExecution(req, context)
    AppRouteMod->>AppRouteMod: ensureUserland()
    AppRouteMod->>Userland: _getUserland() (Turbopack live module)
    AppRouteMod->>AppRouteMod: resolveHandlerFromUserland(req.method, liveUserland)
    AppRouteMod-->>NextReq: Returns PreparedAppRouteExecution
```

Sources: [packages/next/src/server/route-modules/app-route/module.ts:851-913](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-route/module.ts#L851-L913)

### Execution Lifecycle and Dynamic Behavior Modes

Once prepared, `executeRequest` runs the handler within asynchronous storage scopes (`actionAsyncStorage`, `workUnitAsyncStorage`, and `workAsyncStorage`). The request object is wrapped or proxied depending on the route's [dynamic rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/dynamic-rendering) configuration.

| Dynamic Mode | Store Property Mutated | Request Modification |
| :--- | :--- | :--- |
| `force-dynamic` | `workStore.forceDynamic = true` | None (Standard `NextRequest`) |
| `force-static` | `workStore.forceStatic = true` | `new Proxy(req, forceStaticRequestHandlers)` |
| `error` | `workStore.dynamicShouldError = true` | None (Standard `NextRequest`) |
| `auto` / `undefined` | None | `proxyNextRequest(req, workStore)` |

Sources: [packages/next/src/server/route-modules/app-route/module.ts:939-972](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-route/module.ts#L939-L972)

> [!WARNING]
> If a route handler returns an object that is not an instance of the global `Response` class, `validateExecutionResponse` catches it and immediately returns a fallback HTTP 500 internal error response. Furthermore, headers containing `x-middleware-rewrite` or `x-middleware-next` set to `'1'` throw explicit runtime errors because middleware-specific response directives are unsupported inside app route handlers.

Sources: [packages/next/src/server/route-modules/app-route/module.ts:915-937](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/app-route/module.ts#L915-L937)

## Node App Route Invocation

### Request Normalization and Manifest Caching

When an app route is invoked via the Node runtime template, the request enters the exported `handler` function where request metadata is attached and page paths are normalized. Specifically, Turbopack and Webpack route paths ending with `/index` or mapped via multi-zone draft modes are adjusted before invoking `routeModule.prepare(req, res, { srcPage, multiZoneDraftMode })`. This preparation method executes inside a tracing span using `RouteModuleSpan.prepare`, wrapping an internal call to `prepareImpl` which handles directory resolution, cookie patch support, and instrumentation registration.

Sources: [packages/next/src/build/templates/app-route.ts:106-137](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts#L106-L137), [packages/next/src/server/route-modules/route-module.ts:635-648](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L635-L648), [packages/next/src/server/route-modules/route-module.ts:707-735](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L707-L735)

```mermaid
sequenceDiagram
    participant Handler as handler (app-route.ts)
    participant Prepare as prepare (route-module.ts)
    participant PrepareImpl as prepareImpl (route-module.ts)
    participant LoadManifests as loadManifests (route-module.ts)
    participant EdgePreview as getEdgePreviewProps / maybeJSONParse

    Handler->>Prepare: routeModule.prepare(req, res, options)
    Prepare->>PrepareImpl: RouteModuleSpan.prepare trace scope
    PrepareImpl->>LoadManifests: RouteModuleSpan.loadManifests trace scope
    LoadManifests->>EdgePreview: getEdgePreviewProps() (Edge) or loadManifestFromRelativePath (Node)
    EdgePreview-->>LoadManifests: parsed manifests & preview props
    LoadManifests-->>PrepareImpl: manifests object
    PrepareImpl-->>Prepare: prepareResult context
    Prepare-->>Handler: resolved parameters & configuration
```

Sources: [packages/next/src/build/templates/app-route.ts:134-161](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts#L134-L161), [packages/next/src/server/route-modules/route-module.ts:254-256](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L254-L256), [packages/next/src/server/route-modules/route-module.ts:635-648](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L635-L648), [packages/next/src/server/route-modules/route-module.ts:736-741](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L736-L741)

Within `prepareImpl`, manifest files are loaded via `loadManifests` under the `RouteModuleSpan.loadManifests` tracer span. Depending on whether the runtime is set to `edge` or `node`, manifests are either parsed from global variables using `maybeJSONParse` or read from disk relative to the project directory.

> [!NOTE]
> In the [edge runtime](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/edge-runtime), preview properties are retrieved via `getEdgePreviewProps()`, which extracts configuration directly from `process.env.__NEXT_PREVIEW_MODE_ID`, `process.env.__NEXT_PREVIEW_MODE_SIGNING_KEY`, and `process.env.__NEXT_PREVIEW_MODE_ENCRYPTION_KEY`.

Sources: [packages/next/src/server/route-modules/route-module.ts:254-274](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L254-L274), [packages/next/src/server/route-modules/route-module.ts:736-741](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L736-L741), [packages/next/src/server/web/get-edge-preview-props.ts:6-12](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/get-edge-preview-props.ts#L6-L12)

### Response Wrapping and Execution Flow

Once preparation yields a valid configuration, `handler` initializes the [incremental cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/incremental-cache) and sets up global singletons for [server actions](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/server-actions) and client reference manifests if present. Standard Node `IncomingMessage` and `ServerResponse` instances are adapted into `NodeNextRequest`, `NodeNextResponse`, and standard Web `NextRequest` objects via `NextRequestAdapter.fromNodeNextRequest`.

Sources: [packages/next/src/build/templates/app-route.ts:139-278](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts#L139-L278)

| Manifest Category | Node Runtime Source / Method | Edge Runtime Source / Fallback |
| :--- | :--- | :--- |
| **Routes Manifest** | `loadManifestFromRelativePath(ROUTES_MANIFEST)` | `process.env.__NEXT_REWRITES` / defaults |
| **Prerender Manifest** | `loadManifestFromRelativePath(PRERENDER_MANIFEST)` | Static empty routes object |
| **Client Reference** | `evalManifestFromRelativePath(...)` | `self.__RSC_MANIFEST?.[srcPage]` |
| **Server Actions** | `loadManifestFromRelativePath(SERVER_REFERENCE_MANIFEST)` | `maybeJSONParse(self.__RSC_SERVER_MANIFEST)` |

Sources: [packages/next/src/server/route-modules/route-module.ts:275-306](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L275-L306), [packages/next/src/server/route-modules/route-module.ts:337-404](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/route-module.ts#L337-L404)

> [!WARNING]
> If `prepare` returns a falsy result, the handler immediately terminates execution by setting the response status code to `400`, writing `'Bad Request'`, resolving any pending `waitUntil` promises, and returning `null`.

Sources: [packages/next/src/build/templates/app-route.ts:139-144](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/app-route.ts#L139-L144)

## Edge Route Module Adapter

### Streaming and Response Lifecycle

The edge runtime integration wraps route modules via `EdgeRouteModuleWrapper`, bridging standard Web Request/Response types and Next.js internal execution contexts. When an edge app route is invoked, userland configuration and environmental settings such as `__NEXT_BASE_PATH`, `__NEXT_I18N_CONFIG`, and `__NEXT_TRAILING_SLASH` are supplied to the internal adapter handler.

```typescript
export async function handler(
  request: Request,
  ctx: {
    waitUntil?: (prom: Promise<void>) => void
    signal?: AbortSignal
    requestMeta?: RequestMeta
  }
): Promise<Response> {
  const result = await internalHandler({
    request: {
      url: request.url,
      method: request.method,
      headers: toNodeOutgoingHttpHeaders(request.headers),
      nextConfig: {
        basePath: process.env.__NEXT_BASE_PATH,
        i18n: process.env.__NEXT_I18N_CONFIG as any,
        trailingSlash: Boolean(process.env.__NEXT_TRAILING_SLASH),
        experimental: {
          cacheLife: process.env.__NEXT_CACHE_LIFE as any,
          authInterrupts: Boolean(
            process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS
          ),
          clientParamParsingOrigins: process.env
            .__NEXT_CLIENT_PARAM_PARSING_ORIGINS as any,
        },
      },
      page: {
        name: 'VAR_PAGE',
      },
      body:
        request.method !== 'GET' && request.method !== 'HEAD'
          ? (request.body ?? undefined)
          : undefined,
      waitUntil: ctx.waitUntil,
      requestMeta: ctx.requestMeta,
      signal: ctx.signal || new AbortController().signal,
    },
  })

  ctx.waitUntil?.(result.waitUntil)

  return result.response
}
```
Sources: [packages/next/src/build/templates/edge-app-route.ts:42-84](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/edge-app-route.ts#L42-L84)

Within `EdgeRouteModuleWrapper.handler`, dynamic route parameters are normalized from search parameters, cache handlers are initialized with configuration limits, and preview properties are retrieved via `getEdgePreviewProps`.

```typescript
export function getEdgePreviewProps() {
  return {
    previewModeId: process.env.__NEXT_PREVIEW_MODE_ID || '',
    previewModeSigningKey: process.env.__NEXT_PREVIEW_MODE_SIGNING_KEY || '',
    previewModeEncryptionKey:
      process.env.__NEXT_PREVIEW_MODE_ENCRYPTION_KEY || '',
  }
```
Sources: [packages/next/src/server/web/get-edge-preview-props.ts:6-12](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/get-edge-preview-props.ts#L6-L12)

| Environment Variable | Source Property / Fallback | Purpose in Edge Context |
| :--- | :--- | :--- |
| `__NEXT_PREVIEW_MODE_ID` | `process.env.__NEXT_PREVIEW_MODE_ID` | Identifies active draft/preview mode sessions |
| `__NEXT_PREVIEW_MODE_SIGNING_KEY` | `process.env.__NEXT_PREVIEW_MODE_SIGNING_KEY` | Cryptographic signature validation for cookies |
| `__NEXT_PREVIEW_MODE_ENCRYPTION_KEY` | `process.env.__NEXT_PREVIEW_MODE_ENCRYPTION_KEY` | Payload encryption for preview cookies |

Sources: [packages/next/src/server/web/get-edge-preview-props.ts:6-12](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/get-edge-preview-props.ts#L6-L12)

> [!NOTE]
> In the edge runtime, preview props are directly read from runtime environment variables injected by the edge-runtime or replaced during the edge [build pipeline](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/build-system/build-pipeline).

Sources: [packages/next/src/server/web/get-edge-preview-props.ts:1-5](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/get-edge-preview-props.ts#L1-L5)

When handling responses, the wrapper manages asynchronous execution tasks and body streaming lifecycles. If a response body is absent, a deferred close controller timeout dispatches a close event. For streaming responses, `trackStreamConsumed` wraps the response body to guarantee close handlers execute correctly upon consumption.

```typescript
  if (!res.body) {
    // we can delay running it until a bit later --
    // if it's needed, we'll have a `waitUntil` lock anyway.
    setTimeout(() => closeController.dispatchClose(), 0)
  } else {
    // NOTE: if this is a streaming response, onClose may be called later,
    // so we can't rely on `closeController.listeners` -- it might be 0 at this point.
    const trackedBody = trackStreamConsumed(res.body, () =>
      closeController.dispatchClose()
    )
    res = new Response(trackedBody, {
      status: res.status,
      statusText: res.statusText,
      headers: res.headers,
    })
  }

  return res
}
```
Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:158-176](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/edge-route-module-wrapper.ts#L158-L176)

> [!WARNING]
> Because streaming responses can invoke `onClose` at a later time, listeners cannot be tracked by a static count; `trackStreamConsumed` monitors body stream consumption to trigger `closeController.dispatchClose()`.

Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:162-167](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/web/edge-route-module-wrapper.ts#L162-L167)

## Pages API Route Resolution

### Pages API Module Wrapper and Template

The `PagesAPIRouteModule` class manages execution for legacy Pages API routes. Upon instantiation, it validates that `options.userland.default` is a function and wraps the underlying `apiResolver` function using `wrapApiHandler` to assign the route name to root span attributes and trace execution under `NodeSpan.runHandler`.

```typescript
constructor(options: PagesAPIRouteModuleOptions) {
  super(options)

  if (typeof options.userland.default !== 'function') {
    throw new Error(
      `Page ${options.definition.page} does not export a default function.`
    )
  }

  this.apiResolverWrapped = wrapApiHandler(
    options.definition.page,
    apiResolver
  )
}
```
Sources: [packages/next/src/server/route-modules/pages-api/module.ts:115-128](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/route-modules/pages-api/module.ts#L115-L128)

The generated `pages-api` template exports the userland handler and config via hoisting, and executes a request handler function that attaches request metadata, handles Turbopack page normalization for `/index` routes, and calls `routeModule.prepare(req, res, { srcPage })`.

```typescript
export default hoist(userland, 'default')

// Re-export config.
export const config = hoist(userland, 'config')

// Create and export the route module that will be consumed.
const routeModule = new PagesAPIRouteModule({
  definition: {
    kind: RouteKind.PAGES_API,
    page: 'VAR_DEFINITION_PAGE',
    pathname: 'VAR_DEFINITION_PATHNAME',
    // The following aren't used in production.
    bundlePath: '',
    filename: '',
  },
  userland,
  distDir: process.env.__NEXT_RELATIVE_DIST_DIR || '',
  relativeProjectDir: process.env.__NEXT_RELATIVE_PROJECT_DIR || '',
})

export async function handler(
  req: IncomingMessage,
  res: ServerResponse,
  ctx: {
    waitUntil?: (prom: Promise<void>) => void
    requestMeta?: RequestMeta
  }
) {
  if (ctx.requestMeta) {
    setRequestMeta(req, ctx.requestMeta)
  }
  if (routeModule.isDev) {
    addRequestMeta(req, 'devRequestTimingInternalsEnd', process.hrtime.bigint())
  }
  let srcPage = 'VAR_DEFINITION_PAGE'

  // turbopack doesn't normalize `/index` in the page name
  // so we need to to process dynamic routes properly
  // TODO: fix turbopack providing differing value from webpack
  if (process.env.TURBOPACK) {
    srcPage = srcPage.replace(/\/index$/, '') || '/'
  }

  const prepareResult = await routeModule.prepare(req, res, { srcPage })
```
Sources: [packages/next/src/build/templates/pages-api.ts:27-70](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/build/templates/pages-api.ts#L27-L70)

> [!WARNING]
> If `routeModule.prepare` returns a falsy result, the template immediately responds with a `400 Bad Request` status and terminates execution.

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)

### Node.js apiResolver Pipeline and Configuration

The `apiResolver` function orchestrates request parsing and execution. It reads the page configuration from `resolverModule.config`, extracting configuration properties that dictate body parsing behavior, response size limits, and external resolver status.

```typescript
const config: PageConfig = resolverModule.config || {}
const bodyParser = config.api?.bodyParser !== false
const responseLimit = config.api?.responseLimit ?? true
const externalResolver = config.api?.externalResolver || false

```
Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:351-355](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/node/api-resolver.ts#L351-L355)

| PageConfig API Property | Default Value / Evaluation | Description |
| :--- | :--- | :--- |
| `config.api.bodyParser` | `true` (enabled unless explicitly `false` or object with `false`) | Determines whether to parse the incoming request body. |
| `config.api.responseLimit` | `true` (defaults to `RESPONSE_LIMIT_DEFAULT` bytes) | Enforces response content length limits and warns on overflow. |
| `config.api.externalResolver` | `false` | Disables unhandled resolution warnings for external routing middleware. |

Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:352-354](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/node/api-resolver.ts#L352-L354), [packages/next/src/server/api-utils/index.ts:114-114](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/index.ts#L114-L114)

> [!NOTE]
> `apiResolver` uses `setLazyProp` to lazily parse cookies, query parameters, preview data, and draft mode state on `req` only when accessed.

Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:356-375](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/node/api-resolver.ts#L356-L375)

### Body Parsing and Response Helpers

When `bodyParser` is enabled and `apiReq.body` is not already populated, `apiResolver` invokes `parseBody` with the configured size limit, defaulting to `'1mb'`.

```typescript
// Parsing of body
if (bodyParser && !apiReq.body) {
  apiReq.body = await parseBody(
    apiReq,
    config.api && config.api.bodyParser && config.api.bodyParser.sizeLimit
      ? config.api.bodyParser.sizeLimit
      : '1mb'
  )
}
```
Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:377-385](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/node/api-resolver.ts#L377-L385)

Response methods and helpers are monkey-patched or attached directly onto `apiRes` during resolution to provide utility methods.

| Response Helper Method | Implementation Target | Purpose |
| :--- | :--- | :--- |
| `res.status(statusCode)` | `sendStatusCode` | Sets response status code and returns response object. |
| `res.send(data)` | `sendData` | Sends payload data back to the client. |
| `res.json(data)` | `sendJson` | Serializes and sends JSON data. |
| `res.redirect(statusOrUrl, url)` | `redirect` | Writes redirection headers and terminates request. |
| `res.setDraftMode(options)` | `setDraftMode` | Configures draft mode state cookies. |
| `res.setPreviewData(data, options)` | `setPreviewData` | Sets preview data cookies. |
| `res.clearPreviewData(options)` | `clearPreviewData` | Clears preview cookies using expiration dates in the past. |
| `res.revalidate(urlPath, opts)` | `revalidate` | Triggers on-demand revalidation for a path. |

Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:410-426](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/node/api-resolver.ts#L410-L426), [packages/next/src/server/api-utils/index.ts:44-76](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/index.ts#L44-L76), [packages/next/src/server/api-utils/index.ts:119-164](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/server/api-utils/index.ts#L119-L164)

## Related

- [Server Architecture](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/server-architecture)
- [Request Lifecycle](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/request-lifecycle)
- [Edge Runtime](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/edge-runtime)


## Sitemap

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