---
title: "Route Handlers"
description: "Route Handlers in Next.js manage backend data endpoints and request dispatching across distinct execution runtimes and application models. They bridge incoming HTTP traffic with userland route logi..."
last_updated: "2026-09-23T10:52:03.167933+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-router-rendering/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/main/packages/next/src/server/route-modules/app-route/module.ts)
- [packages/next/src/server/base-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/base-server.ts)
- [packages/next/src/server/api-utils/node/api-resolver.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts)
- [packages/next/src/server/app-render/action-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/action-handler.ts)
- [packages/next/src/server/app-render/app-render.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx)
- [packages/next/src/server/route-modules/pages/pages-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/pages/pages-handler.ts)
- [packages/next/src/server/next-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/next-server.ts)
- [packages/next/src/server/lib/router-utils/resolve-routes.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/resolve-routes.ts)
- [packages/next/src/server/lib/router-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-server.ts)
- [packages/next/src/server/web/edge-route-module-wrapper.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts)
- [packages/next/src/server/dev/next-dev-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/next-dev-server.ts)
- [packages/next/src/export/routes/app-route.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts)
- [packages/next/src/server/route-modules/app-page/module.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-page/module.ts)
- [packages/next/src/server/route-modules/pages-api/module.ts](https://github.com/blade47/next.js/blob/main/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/main/packages/next/src/server/api-utils/index.ts)
- [packages/next/src/server/api-utils/node/parse-body.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts)
- [packages/next/src/server/api-utils/node/try-get-preview-data.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts)
- [packages/next/src/server/web/spec-extension/adapters/headers.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts)
</details>

## Overview

Route Handlers in Next.js manage backend data endpoints and request dispatching across distinct execution runtimes and application models. They bridge incoming HTTP traffic with userland route logic, balancing modern Web API Request and Response primitives in App Routes with legacy Node.js message handlers in Pages API routes. By orchestrating payload parsing, header adaptation, security checks, and prerender state tracking, these server modules handle request lifecycles uniformly across environments. Sources: [packages/next/src/server/route-modules/app-route/module.ts:793-978](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L793-L978), [packages/next/src/server/api-utils/node/api-resolver.ts:331-489](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L331-L489), [packages/next/src/server/web/edge-route-module-wrapper.ts:84-177](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L84-L177)

## App Route Module Architecture

### Overview

App Route modules coordinate the complete lifecycle of incoming HTTP requests for App Router endpoints, managing asynchronous module loading, HTTP method resolution, dynamic bailout checks, and response validation. When a request arrives, the route module initializes request storage, evaluates static generation constraints, and executes the target userland handler within nested `AsyncLocalStorage` contexts. Sources: [packages/next/src/server/route-modules/app-route/module.ts:793-978](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L793-L978)

### Execution Lifecycle and Call Chain

The execution pipeline processes incoming requests through a strict sequence of validation, store initialization, and tracer spans before invoking the userland handler. 

`AppRouteRouteModule.handle()` → `this.ensureUserland()` → `resolveHandlerFromUserland()` or `resolveHandler()` → `getImplicitTags()` → `createRequestStoreForAPI()` → `createWorkStore()` → `actionAsyncStorage.run()` → `workUnitAsyncStorage.run()` → `workAsyncStorage.run()` → `tracer.trace()` → `this.do()` Sources: [packages/next/src/server/route-modules/app-route/module.ts:793-952](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L793-L952)

During this flow, `ensureUserland()` guarantees that modules utilizing top-level `await` are fully resolved before execution. Next, `handle()` checks whether non-static methods are present and applies dynamic configuration rules. Sources: [packages/next/src/server/route-modules/app-route/module.ts:793-878](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L793-L878)

### HTTP Method Resolution

Incoming HTTP methods are normalized and matched against exported userland handlers using `resolveHandler(method: string)` or `resolveHandlerFromUserland()`. To prevent Remote Code Execution (RCE), requests with unrecognized HTTP methods are intercepted immediately. Sources: [packages/next/src/server/route-modules/app-route/module.ts:391-396](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L391-L396), [packages/next/src/server/route-modules/app-route/module.ts:813-816](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L813-L816)

| Method Check / Resolver | Fallback Behavior | Target Method / Module | Sources |
|-------------------------|-------------------|------------------------|---------|
| `isHTTPMethod(method)` | Returns `400` status with `null` body | Unrecognized methods | [packages/next/src/server/route-modules/app-route/module.ts:391-396](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L391-L396) |
| Live Userland HMR lookup | Fallfalls back to cached `_userland` module | `liveUserland` or `this._userland` | [packages/next/src/server/route-modules/app-route/module.ts:807-816](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L807-L816) |

Sources: [packages/next/src/server/route-modules/app-route/module.ts:391-396](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L391-L396), [packages/next/src/server/route-modules/app-route/module.ts:807-816](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L807-L816)

### Dynamic Bailout Checks and Configuration

App Route modules evaluate static generation options via `export const dynamic` configurations. The route execution runtime inspects the dynamic mode and modifies the incoming request object or throws a `DynamicServerError` when static generation rules are violated. Sources: [packages/next/src/server/route-modules/app-route/module.ts:869-926](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L869-L926)

| Dynamic Configuration Value | Store Flag Modification | Request Transformation | Sources |
|-----------------------------|-------------------------|------------------------|---------|
| `'force-dynamic'` | `workStore.forceDynamic = true` | Unmodified request (`req`) | [packages/next/src/server/route-modules/app-route/module.ts:890-902](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L890-L902) |
| `'force-static'` | `workStore.forceStatic = true` | Proxied with `forceStaticRequestHandlers` | [packages/next/src/server/route-modules/app-route/module.ts:903-910](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L903-L910) |
| `'error'` | `workStore.dynamicShouldError = true` | Proxied with `requireStaticRequestHandlers` (if static gen) | [packages/next/src/server/route-modules/app-route/module.ts:911-917](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L911-L917) |
| `'auto'` / `undefined` | Tracks dynamic access via store | Proxied via `proxyNextRequest(req, workStore)` | [packages/next/src/server/route-modules/app-route/module.ts:918-923](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L918-L923) |

Sources: [packages/next/src/server/route-modules/app-route/module.ts:889-926](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L889-L926)

> [!WARNING]
> Exporting non-static HTTP methods such as `POST`, `PUT`, `DELETE`, `PATCH`, or `OPTIONS` via `hasNonStaticMethods()` will automatically trigger a `DynamicServerError` if the route is evaluated during static generation. Sources: [packages/next/src/server/route-modules/app-route/module.ts:866-878](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L866-L878), [packages/next/src/server/route-modules/app-route/module.ts:990-999](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L990-L999)

## Edge Runtime Execution Wrapper

### Overview

The `EdgeRouteModuleWrapper` class adapts an `AppRouteRouteModule` for execution inside Edge runtimes, managing request parsing, dynamic route parameter normalization, cache handler initialization, and streaming response body consumption via `CloseController`. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:35-50](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L35-L50)

### Execution Lifecycle and Call-Chain

When an Edge request arrives, `EdgeRouteModuleWrapper.wrap()` instantiates the wrapper and returns an `EdgeHandler` adapter function. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:61-82](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L61-L82)

The execution request proceeds through the internal handler pipeline:
`EdgeRouteModuleWrapper.handler()` → `getServerUtils()` → `routeModule.getNextConfigEdge()` → `initializeCacheHandlers()` → `normalizeDynamicRouteParams()` → `routeModule.handle()` → `trackStreamConsumed()` / `closeController.dispatchClose()`. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:84-176](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L84-L176)

1. `getServerUtils()` builds URL matching utilities using `matcher.isDynamic` and `matcher.definition.pathname`. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:88-96](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L88-L96)
2. `routeModule.getNextConfigEdge()` reads configuration settings for cache limits and life profiles. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:98-100](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L98-L100)
3. `initializeCacheHandlers()` and `setCacheHandler()` configure runtime memory limits and custom cache adapters. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:101-104](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L101-L104)
4. `normalizeDynamicRouteParams()` parses search parameters into dynamic route parameters. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:106-109](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L106-L109)
5. `routeModule.handle()` executes userland handler logic using constructed context parameters. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:116-151](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L116-L151)

> [!NOTE]
> If a response has no body, `setTimeout()` triggers `closeController.dispatchClose()` asynchronously. For streaming responses, `trackStreamConsumed()` wraps `res.body` to invoke `closeController.dispatchClose()` upon consumption. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:159-174](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L159-L174)

Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:84-176](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L84-L176)

### Edge Runtime Configuration Context

The `AppRouteRouteHandlerContext` passed to `routeModule.handle()` configures runtime rendering options and feature flags specifically tailored for Edge environments. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:116-148](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L116-L148)

| Render Option Property | Value | Purpose / Edge Behavior | Sources |
|------------------------|-------|--------------------------|---------|
| `supportsDynamicResponse` | `true` | Enables dynamic response handling | [packages/next/src/server/web/edge-route-module-wrapper.ts:121-122](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L121-L122) |
| `cacheComponents` | `!!process.env.__NEXT_CACHE_COMPONENTS` | Evaluates cache component feature flag | [packages/next/src/server/web/edge-route-module-wrapper.ts:126-126](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L126-L126) |
| `validationLevel` | `'warning'` | Fallback validation level; instant validation is skipped | [packages/next/src/server/web/edge-route-module-wrapper.ts:127-130](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L127-L130) |
| `experimental.authInterrupts` | `!!process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS` | Sets auth interrupts experimental flag | [packages/next/src/server/web/edge-route-module-wrapper.ts:131-133](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L131-L133) |
| `experimental.useCacheTimeout` | `0` | Sentinel value; cache fill times out immediately if read | [packages/next/src/server/web/edge-route-module-wrapper.ts:134-137](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L134-L137) |
| `cacheLifeProfiles` | `nextConfig.cacheLife` | Configures cache life profiles from next config | [packages/next/src/server/web/edge-route-module-wrapper.ts:138-138](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L138-L138) |
| `staticPageGenerationTimeout` | `0` | Sentinel value; static generation does not run in Edge | [packages/next/src/server/web/edge-route-module-wrapper.ts:139-142](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L139-L142) |

Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:116-148](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L116-L148)

> [!CAUTION]
> Both `useCacheTimeout` and `staticPageGenerationTimeout` are hardcoded to `0` in Edge route contexts because Cache Components and static generation are unsupported in the Edge runtime. If invoked, they act as sentinels to immediately surface unexpected access errors. Sources: [packages/next/src/server/web/edge-route-module-wrapper.ts:131-143](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/edge-route-module-wrapper.ts#L131-L143)

## Pages API Resolver Architecture

### Overview

The legacy Pages API infrastructure bridges traditional Node.js request-response lifecycles and Next.js route handling via `PagesAPIRouteModule` and `apiResolver`. When a Pages API request is processed, `PagesAPIRouteModule.render()` initializes performance tracing through `wrapApiHandler()` and delegates directly to `apiResolver()`, supplying the userland module, context properties, and error callbacks. Sources: [packages/next/src/server/api-utils/index.ts:22-37](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L22-L37), [packages/next/src/server/route-modules/pages-api/module.ts:115-161](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/pages-api/module.ts#L115-L161)

### Execution Lifecycle and Call Chain

The resolution flow executes through a deterministic pipeline from module instantiation down to userland handler invocation and telemetry reporting.

`PagesAPIRouteModule` constructor → `wrapApiHandler()` → `PagesAPIRouteModule.render()` → `apiResolver()` → `parseBody()` → `resolver(req, res)` → `onError?.()` Sources: [packages/next/src/server/api-utils/index.ts:22-37](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L22-L37), [packages/next/src/server/api-utils/node/api-resolver.ts:331-489](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L331-L489), [packages/next/src/server/route-modules/pages-api/module.ts:115-161](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/pages-api/module.ts#L115-L161)

1. **Module Construction & Wrapping:** `PagesAPIRouteModule` validates that `options.userland.default` is a function and wraps `apiResolver` using `wrapApiHandler()` to establish root span attributes and trace execution under `NodeSpan.runHandler`. Sources: [packages/next/src/server/api-utils/index.ts:22-37](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L22-L37), [packages/next/src/server/route-modules/pages-api/module.ts:115-128](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/pages-api/module.ts#L115-L128)
2. **Context Setup & Lazy Property Injection:** `apiResolver` extracts page configuration (`resolverModule.config`), attaches lazy cookie parsers, defines writable `req.query` properties to support Express 5 compatibility, and configures preview data getters. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:351-376](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L351-L376)
3. **Payload Parsing & Response Interception:** If `bodyParser` is enabled and `apiReq.body` is unparsed, `parseBody()` processes the payload. Meanwhile, `apiRes.write` and `apiRes.end` are monkey-patched to track cumulative content length against `responseLimit`. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:377-409](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L377-L409)
4. **Handler Invocation & Error Handling:** The interop-defaulted resolver is called with `(req, res)`. If execution throws an `ApiError` or unhandled exception, `onError` telemetry is notified, and appropriate error responses are dispatched based on `dev` and `propagateError` flags. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:428-488](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L428-L488)

> [!WARNING]
> Returning a Web API `Response` object from a Pages API route in the Node.js runtime throws an explicit error instructing developers to use `runtime: "edge"` instead. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:439-444](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L439-L444)

### Page Configuration Options

API routes export a configuration object (`PageConfig`) that dictates runtime behavior inside `apiResolver`.

| Configuration Key | Type | Default Value | Purpose / Behavior | Sources |
|-------------------|------|---------------+--------------------+---------|
| `api.bodyParser` | `boolean \| { sizeLimit?: string \| number }` | `true` | Controls automatic JSON/urlencoded body parsing; set to `false` to consume raw streams | [packages/next/src/server/api-utils/node/api-resolver.ts:352-352](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L352-L352) |
| `api.responseLimit` | `boolean \| string` | `true` (`4MB`) | Configures the maximum response size payload before logging a performance warning | [packages/next/src/server/api-utils/node/api-resolver.ts:353-353](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L353-L353) |
| `api.externalResolver` | `boolean` | `false` | Flags whether another middleware or external library handles response termination, suppressing stalled-request warnings | [packages/next/src/server/api-utils/node/api-resolver.ts:354-354](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L354-L354) |

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

### Design Trade-offs

| Design Choice | Benefit | Cost | Sources |
|---------------|---------|------|---------|
| **Response Method Patching** | Tracks payload byte length transparently without altering userland code signatures | Overrides core `http.ServerResponse` prototype methods (`write`, `end`) per request | [packages/next/src/server/api-utils/node/api-resolver.ts:387-409](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L387-L409) |
| **Lazy Property Evaluation** | Defers expensive cookie parsing and preview data decryption until property access | Introduces getter overhead via `Object.defineProperty` descriptors on request objects | [packages/next/src/server/api-utils/node/api-resolver.ts:357-375](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L357-L375), [packages/next/src/server/api-utils/index.ts:211-231](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L211-L231) |
| **Development Stalled-Request Check** | Detects forgotten responses early during local development | Relies on timing heuristics and pipe-event listeners (`wasPiped`) which may produce false positives | [packages/next/src/server/api-utils/node/api-resolver.ts:429-434](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L429-L434), [packages/next/src/server/api-utils/node/api-resolver.ts:450-454](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L450-L454) |

Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:357-454](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L357-L454), [packages/next/src/server/api-utils/index.ts:211-231](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L211-L231)

## Request Parsing and Body Processing

### Overview

Pages API request ingestion and body processing coordinate through `apiResolver` and `parseBody` to read incoming streams, enforce size limits, and parse payloads according to content-type headers. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:377-385](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L377-L385), [packages/next/src/server/api-utils/node/parse-body.ts:29-66](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L29-L66)

### Call-Chain Execution Walkthrough

1. `apiResolver`: Evaluates page configuration to check if `bodyParser` is enabled (`config.api?.bodyParser !== false`). If enabled and `apiReq.body` is unpopulated, it invokes `parseBody` with the configured size limit or defaults to `'1mb'`. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:351-352](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L351-L352), [packages/next/src/server/api-utils/node/api-resolver.ts:378-384](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L378-L384)
2. `parseBody`: Parses the `content-type` header, extracts the character set encoding (defaulting to `utf-8`), and reads the raw request buffer via `getRawBody` up to the specified size limit. It converts the buffer to a string and branches based on the media type, handing JSON bodies over to `parseJson`. Sources: [packages/next/src/server/api-utils/node/parse-body.ts:29-65](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L29-L65)
3. `parseJson`: Inspects the string length. If empty, it returns an empty object `{}` as a special-case fallback for client-side mistakes; otherwise, it executes `JSON.parse`. Sources: [packages/next/src/server/api-utils/node/parse-body.ts:12-23](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L12-L23)
4. `ApiError`: Thrown when parsing fails or payload sizes exceed limits. Entity size errors throw an `ApiError` with status `413` (`Body exceeded limit`), malformed JSON throws status `400` (`Invalid JSON`), and general body errors throw status `400` (`Invalid body`). Sources: [packages/next/src/server/api-utils/node/parse-body.ts:21-22](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L21-L22), [packages/next/src/server/api-utils/node/parse-body.ts:49-53](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L49-L53)

```mermaid
sequenceDiagram
    participant api-resolver.ts
    participant parse-body.ts
    participant index.ts
    api-resolver.ts->>parse-body.ts: parseBody(apiReq, limit)
    parse-body.ts->>parse-body.ts: parseJson(body)
    parse-body.ts->>index.ts: throw new ApiError(statusCode, message)
```

Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:377-385](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L377-L385), [packages/next/src/server/api-utils/node/parse-body.ts:12-66](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L12-L66), [packages/next/src/server/api-utils/index.ts:176-183](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L176-L183)

### Content Type Parsing Matrix

| Media Type Match | Parsing Mechanism | Return Output | Sources |
|------------------|-------------------|---------------+---------|
| `application/json`, `application/ld+json` | `parseJson()` via `JSON.parse` | Object / Parsed JSON | [packages/next/src/server/api-utils/node/parse-body.ts:58-59](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L58-L59) |
| `application/x-www-form-urlencoded` | `querystring.decode()` | Key-value dictionary | [packages/next/src/server/api-utils/node/parse-body.ts:60-62](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L60-L62) |
| Other types (fallback) | Raw string conversion | `string` | [packages/next/src/server/api-utils/node/parse-body.ts:63-65](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L63-L65) |

Sources: [packages/next/src/server/api-utils/node/parse-body.ts:58-65](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L58-L65)

> [!WARNING]
> If `raw-body` throws an error where `e.type` equals `'entity.too.large'`, `parseBody` catches it and raises an `ApiError` with status code `413`. Any other failure yields status code `400` with message `'Invalid body'`. Sources: [packages/next/src/server/api-utils/node/parse-body.ts:48-54](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/parse-body.ts#L48-L54)

## Preview Data and Context Resolution

### Overview

Preview data resolution and request header adapters handle the decryption of preview cookies, evaluation of on-demand revalidation flags, and normalization of Node.js raw request headers into web-standard interfaces. The `apiResolver` function initializes lazy properties on the incoming request object for cookies, query parameters, preview data, and draft mode state. When `previewData` is accessed, it invokes `tryGetPreviewData`, which inspects incoming headers to determine if an on-demand revalidation request is underway. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:356-376](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L356-L376), [packages/next/src/server/api-utils/node/try-get-preview-data.ts:17-30](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L17-L30)

### Call-Chain Execution Walkthrough

1. `apiResolver` sets up lazy evaluation for `previewData` by calling `tryGetPreviewData(req, res, apiContext, !!apiContext.multiZoneDraftMode)`. Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:367-369](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L367-L369)
2. `tryGetPreviewData` inspects request headers by calling `checkIsOnDemandRevalidate(req.headers, options)`. Sources: [packages/next/src/server/api-utils/node/try-get-preview-data.ts:25-28](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L25-L28)
3. `checkIsOnDemandRevalidate` verifies whether `rawHeaders.get` exists, and if so, invokes `HeadersAdapter.from(rawHeaders)` to wrap standard headers. Sources: [packages/next/src/server/api-utils/index.ts:86-87](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L86-L87)
4. `HeadersAdapter.from` checks if the input is already an instance of `Headers`; if it is a plain `IncomingHttpHeaders` object, it instantiates and returns a new `HeadersAdapter`. Sources: [packages/next/src/server/web/spec-extension/adapters/headers.ts:155-159](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L155-L159)
5. `checkIsOnDemandRevalidate` subsequently invokes `.get()` on the header collection, which calls `HeadersAdapter.prototype.get` to retrieve header values, executing `this.merge(value)` if multiple values exist as an array. Sources: [packages/next/src/server/api-utils/index.ts:89-90](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L89-L90), [packages/next/src/server/web/spec-extension/adapters/headers.ts:143-147](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L143-L147), [packages/next/src/server/web/spec-extension/adapters/headers.ts:176-181](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L176-L181)

```mermaid
sequenceDiagram
    participant api-resolver.ts
    participant try-get-preview-data.ts
    participant index.ts
    participant headers.ts
    api-resolver.ts->>try-get-preview-data.ts: tryGetPreviewData(req, res, apiContext, multiZoneDraftMode)
    try-get-preview-data.ts->>index.ts: checkIsOnDemandRevalidate(req.headers, options)
    index.ts->>headers.ts: HeadersAdapter.from(rawHeaders)
    headers.ts->>headers.ts: HeadersAdapter.prototype.get(name)
    headers.ts->>headers.ts: HeadersAdapter.prototype.merge(value)
```

Sources: [packages/next/src/server/api-utils/node/api-resolver.ts:367-369](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts#L367-L369), [packages/next/src/server/api-utils/node/try-get-preview-data.ts:25-28](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L25-L28), [packages/next/src/server/api-utils/index.ts:86-87](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L86-L87), [packages/next/src/server/web/spec-extension/adapters/headers.ts:143-147](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L143-L147), [packages/next/src/server/web/spec-extension/adapters/headers.ts:155-159](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L155-L159), [packages/next/src/server/web/spec-extension/adapters/headers.ts:176-181](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L176-L181)

### Preview Data Constants and Symbols

| Constant Name | Value / Identifier | Description | Sources |
|---------------|-------------------|-------------|---------|
| `COOKIE_NAME_PRERENDER_BYPASS` | `__prerender_bypass` | Cookie name used for preview mode bypass and draft mode ID storage. | [packages/next/src/server/api-utils/index.ts:111](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L111) |
| `COOKIE_NAME_PRERENDER_DATA` | `__next_preview_data` | Cookie name used for encrypted preview data payloads. | [packages/next/src/server/api-utils/index.ts:112](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L112) |
| `SYMBOL_PREVIEW_DATA` | `Symbol(__next_preview_data)` | Request property symbol used to cache resolved preview data. | [packages/next/src/server/api-utils/index.ts:116](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L116) |
| `SYMBOL_CLEARED_COOKIES` | `Symbol(__prerender_bypass)` | Response property symbol indicating preview cookies have been cleared. | [packages/next/src/server/api-utils/index.ts:117](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L117) |
| `PRERENDER_REVALIDATE_HEADER` | `x-matched-path` or revalidate header constant | Header key identifying on-demand revalidation requests. | [packages/next/src/server/api-utils/index.ts:7-8](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L7-L8) |

Sources: [packages/next/src/server/api-utils/index.ts:7-8](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L7-L8), [packages/next/src/server/api-utils/index.ts:111-117](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/index.ts#L111-L117)

### Design Trade-Offs in Header Adaptation

| Design Choice | Benefit | Cost | Sources |
|---------------|---------|------|---------|
| `HeadersAdapter` proxy wrapping over `IncomingHttpHeaders` | Allows case-insensitive header lookups matching web standard APIs without copying large header dictionaries. | Proxy trap overhead on every header access (`get`, `set`, `has`, `deleteProperty`). | [packages/next/src/server/web/spec-extension/adapters/headers.ts:36-114](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L36-L114) |
| Request caching via `SYMBOL_PREVIEW_DATA` | Avoids repeated cookie parsing, JWT signature verification, and secret decryption within a single request lifecycle. | Ties cached preview state directly to the Node `IncomingMessage` request instance lifecycle. | [packages/next/src/server/api-utils/node/try-get-preview-data.ts:34-36](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L34-L36), [packages/next/src/server/api-utils/node/try-get-preview-data.ts:111-114](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L111-L114) |
| Strict cookie parity checks (`previewModeId` vs `tokenPreviewData`) | Automatically purges invalid or half-set preview sessions to prevent state corruption. | Discards session data if either cookie is missing, causing unexpected logouts during partial cookie delivery. | [packages/next/src/server/api-utils/node/try-get-preview-data.ts:68-82](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L68-L82) |

Sources: [packages/next/src/server/api-utils/node/try-get-preview-data.ts:34-36](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L34-L36), [packages/next/src/server/api-utils/node/try-get-preview-data.ts:68-82](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L68-L82), [packages/next/src/server/api-utils/node/try-get-preview-data.ts:111-114](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L111-L114), [packages/next/src/server/web/spec-extension/adapters/headers.ts:36-114](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/headers.ts#L36-L114)

> [!WARNING]
> If an on-demand revalidation request is detected via `checkIsOnDemandRevalidate`, `tryGetPreviewData` immediately short-circuits and returns `false`, disabling preview mode for that request to prevent revalidation requests from executing under user preview contexts. Sources: [packages/next/src/server/api-utils/node/try-get-preview-data.ts:25-30](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L25-L30)

> [!CAUTION]
> If only one of the two preview cookies (`COOKIE_NAME_PRERENDER_BYPASS` or `COOKIE_NAME_PRERENDER_DATA`) is present, `tryGetPreviewData` invokes `clearPreviewData(res)` unless `multiZoneDraftMode` is enabled, wiping both cookies from the response headers. Sources: [packages/next/src/server/api-utils/node/try-get-preview-data.ts:68-74](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/try-get-preview-data.ts#L68-L74)

## Static Export and Response Handling

### Overview

During static generation or build-time export, Route Handlers (`app-route`) must be orchestrated to serialize their output bodies, metadata, and revalidation parameters into designated files via the multi-file writer. This orchestration coordinates request adaptation, module loading, static generation validation, execution error handling, and header serialization.

Sources: [packages/next/src/export/routes/app-route.ts:36-182](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L36-L182)

### Call-Chain Execution Walkthrough

The export orchestration for app route modules processes requests through a sequence of normalization, validation, and storage execution steps:

`exportAppRoute()` → `module.ensureUserland()` → `isStaticGenEnabled()` → `module.handle()` → `afterRunner.executeAfter()` → `fileWriter.append()`

1. **`exportAppRoute()`**: Initializes the absolute request URL, wraps the node request using `NextRequestAdapter.fromNodeNextRequest`, and instantiates an `AfterRunner` and `AppRouteRouteHandlerContext`.
Sources: [packages/next/src/export/routes/app-route.ts:58-98](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L58-L98)

2. **`module.ensureUserland()`**: Ensures that asynchronous modules (including those with top-level `await`) are fully resolved before the route handler is invoked.
Sources: [packages/next/src/export/routes/app-route.ts:101-105](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L101-L105)

3. **`isStaticGenEnabled()`**: Inspects the loaded userland module to check if static generation is permitted, bypassing this check if the route is a metadata route or if `cacheComponents` is active.
Sources: [packages/next/src/export/routes/app-route.ts:106-122](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L106-L122)

4. **`module.handle()`**: Dispatches the request through the handler pipeline, verifying that the returned value is a valid `Response` object and collecting revalidation tags and times into `renderOpts`.
Sources: [packages/next/src/server/route-modules/app-route/module.ts:749-791](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L749-L791), [packages/next/src/export/routes/app-route.ts:124-124](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L124-L124)

5. **`afterRunner.executeAfter()`**: Executes any deferred callbacks registered during the route handler lifecycle prior to writing out final binary blobs and metadata.
Sources: [packages/next/src/export/routes/app-route.ts:131-135](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L131-L135)

6. **`fileWriter.append()`**: Serializes the response body into a `_body` suffix file and writes response headers and status codes into a `_meta` suffix file.
Sources: [packages/next/src/export/routes/app-route.ts:161-169](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L161-L169)

### Export Constants and File Suffixes

| Constant / Identifier | Value | Purpose | Sources |
|-----------------------|-------|---------|---------|
| `ExportedAppRouteFiles.BODY` | `'BODY'` | Key representing the exported body file stream type. | [packages/next/src/export/routes/app-route.ts:31-34](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L31-L34) |
| `ExportedAppRouteFiles.META` | `'META'` | Key representing the exported metadata file type. | [packages/next/src/export/routes/app-route.ts:31-34](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L31-L34) |
| `NEXT_BODY_SUFFIX` | `'.body'` | File suffix extension appended when writing exported route bodies. | [packages/next/src/lib/constants.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/constants.ts), [packages/next/src/export/routes/app-route.ts:8-11](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L8-L11) |
| `NEXT_META_SUFFIX` | `'.meta'` | File suffix extension appended when writing exported route headers and status metadata. | [packages/next/src/lib/constants.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/lib/constants.ts), [packages/next/src/export/routes/app-route.ts:8-11](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L8-L11) |

Sources: [packages/next/src/export/routes/app-route.ts:8-34](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L8-L34)

> [!CAUTION]
> If a route handler returns a response status code greater than or equal to 400 (except for status 404), `exportAppRoute` immediately intercepts the response and returns a revalidation value of `0`, preventing erroneous error pages from being cached as static output. Sources: [packages/next/src/export/routes/app-route.ts:126-129](https://github.com/blade47/next.js/blob/main/packages/next/src/export/routes/app-route.ts#L126-L129)

> [!WARNING]
> Route Handlers enforce strict return verification: if a handler resolves without returning a valid instance of the web `Response` object, `AppRouteRouteModule.handle` throws an explicit error indicating that a `Response` or `NextResponse` must be returned across all execution branches. Sources: [packages/next/src/server/route-modules/app-route/module.ts:750-765](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts#L750-L765)

## Related

- [Server Request Lifecycle](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/server-request-lifecycle)
- [Web Spec Adapters](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/edge-and-sandbox/web-spec-adapters)


## Sitemap

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