Architecture Overview
Server Runtime
Rendering Pipeline
Client Navigation
Caching and Export
Development Tools
Build and Configuration
Ecosystem Packages
Testing Infrastructure
How It Works
The following files were used as context for generating this wiki page:
Web Spec Adapters form the bridging layer in Next.js that unifies native Node.js HTTP primitives (IncomingMessage, ServerResponse) and Web Standard Fetch API primitives (Request, Response, Headers, URL) across dual execution runtimes: the traditional Node.js server runtime and the lightweight Edge runtime. Because Next.js features such as Middleware, App Route handlers, and Server Actions expose standard Web API interfaces (NextRequest, NextResponse), the server infrastructure requires a robust translation subsystem to wrap, normalize, and unwrap requests and responses irrespective of where code executes.
The architecture bridges the mismatch between Node.js event-emitter based stream mechanics and WHATWG Fetch streaming specifications. It achieves this by introducing runtime HTTP wrappers (NodeNextRequest, NodeNextResponse, WebNextRequest, WebNextResponse), request adapters (NextRequestAdapter), and spec-extension classes (NextRequest, NextResponse) equipped with proxy-based casing preservation, cookie interception, and abort signal tracking.
Sources: packages/next/src/server/base-http/node.ts:1-170, packages/next/src/server/base-http/web.ts:1-141
By decoupling application handlers from platform-specific IO objects, Web Spec Adapters allow user code to consume standard Web APIs while giving Next.js fine-grained control over header casing, stream consumption tracking, request lifecycle abortion, and telemetry context propagation.
Next.js defines runtime-specific HTTP representations in packages/next/src/server/base-http/node.ts and packages/next/src/server/base-http/web.ts. In the Node.js runtime, NodeNextRequest wraps IncomingMessage and provides streaming conversion via stream(), while NodeNextResponse wraps ServerResponse to manage headers, status codes, and completion listeners. In the Edge runtime, WebNextRequest and WebNextResponse manage standard Request, TransformStream, and Response objects.
Sources: packages/next/src/server/base-http/node.ts:19-169, packages/next/src/server/base-http/web.ts:11-140
Sources: packages/next/src/server/base-http/node.ts:19-169, packages/next/src/server/base-http/web.ts:11-140, packages/next/src/server/web/spec-extension/request.ts:14-105, packages/next/src/server/web/spec-extension/response.ts:36-153
The concrete implementations are selected according to environment checks defined in packages/next/src/server/base-http/helpers.ts. When process.env.NEXT_RUNTIME === 'edge', type guards isWebNextRequest and isWebNextResponse return true. Otherwise, isNodeNextRequest and isNodeNextResponse evaluate to true for Node.js execution.
The NextRequestAdapter class in packages/next/src/server/web/spec-extension/adapters/next-request.ts converts runtime request representations into NextRequest instances suitable for route handlers, middleware, and server rendering.
NextRequestAdapter.fromBaseNextRequest() evaluates process.env.NEXT_RUNTIME === 'edge' alongside isWebNextRequest and isNodeNextRequest to branch between Edge and Node conversion logic.let body = null) per HTTP specifications. For Node requests, non-GET/HEAD bodies are passed directly with duplex: 'half'.request.url is absolute, a URL object is instantiated directly. If relative, metadata (initURL from request meta) or a dummy base (http://n) is supplied to construct a valid absolute URL.signalFromNodeResponse() inspects the underlying writable response. If already errored or destroyed, an aborted signal is immediately returned; otherwise, an AbortController listens for the close event on the Node ServerResponse. If close fires before response.writableFinished, a ResponseAborted error aborts the controller.Warning
Request bodies cannot be passed if the associated abort signal has already been triggered; doing so raises a Request body was disturbed error in the underlying Fetch implementation.
Sources: packages/next/src/server/web/spec-extension/adapters/next-request.ts:16-54, packages/next/src/server/web/spec-extension/adapters/next-request.ts:80-150
Standard WHATWG Headers objects lowercase all header names. However, Node.js and proxy environments frequently require preserving or querying original casing, or interfacing directly with raw IncomingHttpHeaders objects without expensive cloning.
HeadersAdapter in packages/next/src/server/web/spec-extension/adapters/headers.ts solves this via a JavaScript Proxy. The proxy intercepts get, set, has, and deleteProperty traps, normalizes incoming property lookups to lower case, and searches the underlying Node headers keys to find the original casing:
const original = Object.keys(headers).find(
(o) => o.toLowerCase() === lowercased
)If found, operations execute against the original casing key. Furthermore, HeadersAdapter.seal() wraps a Headers instance in a proxy that intercepts mutating methods (append, delete, set) and immediately invokes ReadonlyHeadersError.callable() to enforce read-only semantics for server components and cached contexts.
Sources: packages/next/src/server/web/spec-extension/adapters/headers.ts:8-18, packages/next/src/server/web/spec-extension/adapters/headers.ts:116-134
NextResponse extends the global Web Response class, adding helper static methods (json, redirect, rewrite, next) and embedded cookie/middleware interception logic.
When custom request headers are modified via middleware response initializers (MiddlewareResponseInit), handleMiddlewareField() iterates over provided request headers, prefixes them with x-middleware-request-, and sets x-middleware-override-headers so downstream server layers can reconstruct modified request states.
Note
When NextResponse.redirect() is invoked, the status code is validated against a strict set of redirect codes (301, 302, 303, 307, 308). Passing an unsupported status code throws a RangeError.
Once a route handler or middleware returns a standard Response object, sendResponse() in packages/next/src/server/send-response.ts transmits the payload across the underlying Node.js HTTP transport.
sendResponse handles multi-value header normalization explicitly. While most headers overwrite existing values or check presence, headers with multiple values allowed (set-cookie, www-authenticate, proxy-authenticate, vary) are split using splitCookiesString() and appended individually to the underlying Node ServerResponse.
In the Edge Runtime sandbox (packages/next/src/server/web/sandbox/context.ts), global objects such as fetch, Request, and Response are patched to inject Next.js-specific capabilities like inline asset fetching, next fetch options configuration, and validation checks.
const __Request = context.Request
context.Request = class extends __Request {
next?: NextFetchRequestConfig | undefined
constructor(input: URL | RequestInfo, init?: RequestInit | undefined) {
const url = typeof input !== 'string' && 'url' in input ? input.url : String(input)
validateURL(url)
super(input, init)
this.next = init?.next
}
}Furthermore, unsupported Node.js APIs (such as filesystem or cluster modules) are stubbed via __import_unsupported(), throwing informative runtime errors when invoked inside Edge functions.
The following reference table outlines the correspondence between core Web Spec Adapter classes, their underlying transport objects, and their target execution runtimes.
Sources: packages/next/src/server/base-http/node.ts:19-169, packages/next/src/server/base-http/web.ts:11-140, packages/next/src/server/web/spec-extension/adapters/headers.ts:28-231