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:
Server Testing Utilities provide experimental testing APIs and primitives designed to evaluate and verify Next.js server behavior—such as custom configuration routes and middleware matching rules—directly in unit and integration test suites without requiring a live server instance. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:78-90, packages/next/src/experimental/testing/server/middleware-testing-utils.ts:19-31
By combining in-memory streaming request-response primitives, asynchronous request context propagation across Node.js and Edge runtimes, and local proxy daemons for testmode fetch interception, these utilities enable robust end-to-end test harnesses and deterministic assertions over routing, headers, and request lifecycles. Sources: packages/next/src/experimental/testmode/fetch.ts:127-142, packages/next/src/experimental/testmode/server.ts:36-47, packages/next/src/experimental/testmode/proxy/server.ts:18-81, packages/next/src/server/lib/mock-request.ts:25-138, packages/next/src/experimental/testmode/context.ts:14-40
The testing package exposes its public surface through explicit export declarations from entry points at next/experimental/testing/server and CommonJS mappings at next/experimental/testing/server.js. These entry points re-export configuration testing helpers, middleware matching utilities, and request construction functions designed to facilitate server behavior assertions. Sources: packages/next/experimental/testing/server.js:1-2, packages/next/src/experimental/testing/server/index.ts:1-4
The module exports several core helper functions and types across its underlying utility files. The constructRequest function initializes an in-memory BaseNextRequest object wrapping NodeNextRequest and MockedRequest instances based on a provided URL, HTTP headers, and cookie dictionary. Response evaluation helpers include getRedirectUrl for extracting the location header, getRewrittenUrl for reading the x-middleware-rewrite header, and isRewrite for checking rewrite status. Sources: packages/next/src/experimental/testing/server/utils.ts:8-56
Sources: packages/next/experimental/testing/server.js:1-2, packages/next/src/experimental/testing/server/index.ts:1-4, packages/next/src/experimental/testing/server/utils.ts:8-56
Next.js provides testing utilities for evaluating custom headers, redirects, and rewrites directly from a Next.js configuration object without needing to boot up a live server or listen on a network socket. The central entry point for this capability is unstable_getResponseFromNextConfig, which loads custom routes, matches incoming request parameters against path regular expressions and conditional constraints, and produces an authoritative NextResponse. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:57-90
When evaluating a request against a configuration via unstable_getResponseFromNextConfig, execution follows a strict sequence through configuration normalization, route compilation, and rule matching:
unstable_getResponseFromNextConfig() → parse() / constructRequest() → normalizeConfig() → loadCustomRoutes() → buildCustomRoute() → matchRoute() → matchHas() → matchRouteAndGetDestination() → prepareDestination() → NextResponse.redirect() or NextResponse.rewrite()
parse(url, true) and wrapped into an in-memory BaseNextRequest via constructRequest. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:91-92normalizeConfig processes the nextConfig input under PHASE_PRODUCTION_BUILD, after which loadCustomRoutes extracts the raw route definitions. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:93-97buildCustomRoute. Redirect routes receive exclusion filters for /_next/. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:99-110matchRoute succeeds, all associated header key-value pairs are accumulated into respHeaders. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:112-119redirectRoutes and then rewriteRoutes. matchRouteAndGetDestination calls matchRoute and prepares the final destination URL via prepareDestination. If matched, a corresponding NextResponse.redirect or NextResponse.rewrite with accumulated headers is returned immediately. If no rules match, a default 200 response is returned. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:120-166Note
matchRoute validates both the compiled regular expression (route.regex) and the path-to-regexp parser (matcharams>(route.source)). If the regex matches but parameter extraction fails unexpectedly, an explicit error is thrown. Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:29-54
Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:29-90, packages/next/src/experimental/testing/server/utils.ts:8-56
Sources: packages/next/src/experimental/testing/server/utils.ts:8-32, packages/next/src/experimental/testing/server/config-testing-utils.ts:57-166
import { unstable_getResponseFromNextConfig } from '../../../src/experimental/testing/server/config-testing-utils'
import { getRedirectUrl, isRewrite, getRewrittenUrl } from '../../../src/experimental/testing/server/utils'
async function runTest() {
// Evaluate a custom redirect rule defined directly in config
const redirectResponse = await unstable_getResponseFromNextConfig({
url: 'https://nextjs.org/old-blog/hello-world',
nextConfig: {
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/blog/:slug',
permanent: false,
},
]
},
},
})
console.log('Status:', redirectResponse.status) // 307 or 308 depending on permanent flag
console.log('Redirect URL:', getRedirectUrl(redirectResponse)) // https://nextjs.org/blog/hello-world
// Evaluate custom headers and rewrites
const rewriteResponse = await unstable_getResponseFromNextConfig({
url: 'https://nextjs.org/profile',
headers: { 'x-custom-header': 'test-value' },
cookies: { session: 'xyz' },
nextConfig: {
async headers() {
return [
{
source: '/profile',
headers: [{ key: 'x-tested', value: 'true' }],
},
]
},
async rewrites() {
return {
beforeFiles: [],
afterFiles: [
{
source: '/profile',
destination: '/user-profile',
},
],
fallback: [],
}
},
},
})
console.log('Is Rewrite:', isRewrite(rewriteResponse)) // true
console.log('Rewritten URL:', getRewrittenUrl(rewriteResponse)) // https://nextjs.org/user-profile
console.log('Header x-tested:', rewriteResponse.headers.get('x-tested')) // true
}
runTest()Sources: packages/next/src/experimental/testing/server/config-testing-utils.ts:57-166, packages/next/src/experimental/testing/server/utils.ts:8-56
The unstable_doesMiddlewareMatch utility evaluates whether a specific middleware configuration's matcher rules match a given URL, set of headers, and cookies. This allows unit tests to assert that middleware executes precisely when intended without booting a server runtime.
Evaluating a matcher rule involves constructing a mock request object, parsing URL search parameters, compiling matchers, and executing the compiled route matching function:
unstable_doesMiddlewareMatch() receives the config, url, headers, cookies, and optional nextConfig. Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:19-31config.matcher is defined; if absent, it returns true immediately. Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:32-34getMiddlewareMatchers() processes the configuration matcher input alongside nextConfig. Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:35getMiddlewareRouteMatcher() compiles the generated matchers into an executable routing function (routeMatchFn). Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:36parseUrl(url) extracts the pathname and searchParams. Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:37constructRequest() builds a BaseNextRequest wrapping a NodeNextRequest and MockedRequest containing the supplied URL, populated headers, and serialized cookies. Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:38routeMatchFn(pathname, request, Object.fromEntries(searchParams)) evaluates the pathname, request context, and query parameters against the compiled matcher rules. Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:39Note
If a middleware configuration omits the matcher property entirely, unstable_doesMiddlewareMatch defaults to returning true for any incoming URL and request combination.
Sources: packages/next/src/experimental/testing/server/middleware-testing-utils.ts:32-34
The testing suite provides helper utilities for generating request mocks and inspecting redirect or rewrite outcomes:
constructRequest(): Normalizes incoming headers, automatically injects a host header derived from the URL if missing, and serializes a record of cookies into a semicolon-delimited cookie header string before instantiating a NodeNextRequest and MockedRequest. Sources: packages/next/src/experimental/testing/server/utils.ts:8-32getRedirectUrl(): Inspects a NextResponse and returns the value of the location header, or null if absent. Sources: packages/next/src/experimental/testing/server/utils.ts:38-40isRewrite(): Returns a boolean indicating whether the response contains a rewrite header. Sources: packages/next/src/experimental/testing/server/utils.ts:46-48getRewrittenUrl(): Returns the value of the x-middleware-rewrite header from a NextResponse, or null if not a rewrite. Sources: packages/next/src/experimental/testing/server/utils.ts:54-56The types accepted by the middleware matcher evaluation function include the source configuration and matcher inputs:
Next.js provides in-memory streaming mock request and response primitives (MockedRequest and MockedResponse) to power server-side unit tests, routing suites, and internal rendering mechanisms without requiring a bound network socket or external HTTP server. These classes implement Node.js Stream.Readable and Stream.Writable interfaces alongside IncomingMessage and ServerResponse contracts.
The constructRequest utility function and the createRequestResponseMocks factory configure and instantiate these primitives for test execution.
constructRequest() accepts an object containing a url, optional headers, and optional cookies. Sources: packages/next/src/experimental/testing/server/utils.ts:8-16headers is initialized and automatically derives headers.host from the URL via parseUrl(url)?.host if not explicitly provided. Sources: packages/next/src/experimental/testing/server/utils.ts:17-22cookies are supplied as a record, it maps each entry to a name=value pair, joins them with semicolons, and sets headers.cookie. Sources: packages/next/src/experimental/testing/server/utils.ts:23-30MockedRequest with the URL, headers, and method set to 'GET'. Sources: packages/next/src/experimental/testing/server/utils.ts:31MockedRequest inside a NodeNextRequest instance and returns it as a BaseNextRequest. Sources: packages/next/src/experimental/testing/server/utils.ts:31, packages/next/src/server/lib/mock-request.ts:478-497The configuration options accepted by MockedRequest, MockedResponse, and the overarching mock factory govern the behavior of the streaming primitives.
Sources: packages/next/src/server/lib/mock-request.ts:17-23, packages/next/src/server/lib/mock-request.ts:140-146, packages/next/src/server/lib/mock-request.ts:468-476
Warning
Unimplemented Node.js IncomingMessage or ServerResponse methods such as aborted, complete, trailers, and setTimeout throw a Method not implemented error when invoked on mock primitives. Ensure test code restricts its usage to supported streaming and property access APIs.
Sources: packages/next/src/server/lib/mock-request.ts:111-138, packages/next/src/server/lib/mock-request.ts:455-466
Sources: packages/next/src/server/lib/mock-request.ts:39-77, packages/next/src/server/lib/mock-request.ts:162-168
The testmode infrastructure in Next.js manages request context propagation and API interception across both Node.js and Edge runtimes. Using AsyncLocalStorage, request metadata containing test information is maintained throughout asynchronous execution flows. Request readers extract configuration and headers such as next-test-proxy-port and next-test-data to populate the TestReqInfo context store.
The context system defines the TestRequestReader interface for abstracting header extraction and URL retrieval across different request objects. Two distinct readers are implemented: one for standard Request objects in fetch/Edge contexts, and another for Node.js IncomingMessage instances.
Sources: packages/next/src/experimental/testmode/fetch.ts:12-19, packages/next/src/experimental/testmode/server.ts:8-22
Note
getTestReqInfo checks AsyncLocalStorage (testStorage.getStore()) first; if no store is active, it falls back to parsing headers directly from the provided request object using the supplied reader.
Sources: packages/next/src/experimental/testmode/context.ts:42-54
Global fetch calls and outbound HTTP requests are intercepted to route test operations through a local proxy daemon. In Node.js environments, interceptTestApis sets up both interceptFetch and interceptHttpGet (powered by @mswjs/interceptors), returning a combined cleanup function. Sources: packages/next/src/experimental/testmode/server.ts:24-34
The execution flow for an intercepted fetch request proceeds through the following call chain:
testFetch(input, init) intercepts global fetch invocations, ignoring requests marked with internal flags. Sources: packages/next/src/experimental/testmode/fetch.ts:127-138handleFetch(originalFetch, request) calls getTestInfo(request, reader) to retrieve test context. Sources: packages/next/src/experimental/testmode/fetch.ts:85-93buildProxyRequest(testData, request) constructs a ProxyFetchRequest payload containing serialized headers, method, body, and stack traces gathered via getTestStack(). Sources: packages/next/src/experimental/testmode/fetch.ts:39-75, packages/next/src/experimental/testmode/fetch.ts:95-96originalFetch to the local proxy port (http://localhost:${proxyPort}) as a POST payload. Sources: packages/next/src/experimental/testmode/fetch.ts:98-105api field (continue, abort, unhandled, or fetch), directing whether the original request is executed, an error is thrown, or a mocked response is built via buildResponse(). Sources: packages/next/src/experimental/testmode/fetch.ts:110-124Both Node.js and Edge entry points provide request handler wrappers to ensure AsyncLocalStorage is correctly populated before handler execution. In Edge runtimes (server-edge.ts), wrapRequestHandler wraps request handlers with withRequestContext. In Node.js, worker and server request handlers (wrapRequestHandlerWorker, wrapRequestHandlerNode) bind incoming requests to withRequest.
Sources: packages/next/src/experimental/testmode/server-edge.ts:8-12, packages/next/src/experimental/testmode/server.ts:36-47, packages/next/src/server/web/adapter.ts:97-108
Warning
When process.env.NEXT_PRIVATE_TEST_PROXY is set to 'true', Edge route adapters automatically trigger ensureTestApisIntercepted(), activating test mode and wrapping OpenTelemetry context propagators with test request context bindings.
Sources: packages/next/src/server/web/adapter.ts:97-108
The testmode proxy server operates as an isolated local HTTP daemon (http.createServer) and fetching bridge that facilitates end-to-end test harnesses by intercepting and routing test API requests. It listens on an ephemeral loopback port (::) and coordinates with FetchHandler implementations to mock, continue, or abort outbound network operations.
Sources: packages/next/src/experimental/testmode/proxy/server.ts:1-64, packages/next/src/experimental/testmode/proxy/fetch-api.ts:1-15
When an incoming request hits the testmode proxy daemon, it flows through a strict parsing and dispatch sequence:
readBody(req) asynchronously aggregates incoming request stream chunks into a single Buffer. Sources: packages/next/src/experimental/testmode/proxy/server.ts:8-16JSON.parse(...) decodes the buffer into a ProxyRequest payload; malformed payloads immediately receive a 400 status code and terminate. Sources: packages/next/src/experimental/testmode/proxy/server.ts:30-37json.api discriminator field and dispatches to handleFetch(json, onFetch) when api === 'fetch'. Sources: packages/next/src/experimental/testmode/proxy/server.ts:39-47buildRequest(req) reconstructs a standard Request object from the serialized headers and base64-encoded body, passing it along with testData to the registered FetchHandler. Sources: packages/next/src/experimental/testmode/proxy/fetch-api.ts:16-24, packages/next/src/experimental/testmode/proxy/fetch-api.ts:52-58buildResponse(response) translates the handler's FetchHandlerResult into a structured ProxyResponse object (unhandled, abort, continue, or a serialized fetch response containing status, base64 body, and headers). Sources: packages/next/src/experimental/testmode/proxy/fetch-api.ts:26-50200 status code and application/json content type. Sources: packages/next/src/experimental/testmode/proxy/server.ts:55-58The proxy module exports core factory functions and type definitions to manage proxy server instances and wire communication protocols between test harnesses and Next.js runtimes.
Sources: packages/next/src/experimental/testmode/proxy/types.ts:1-60, packages/next/src/experimental/testmode/proxy/fetch-api.ts:4-14
Note
The fetchWith method automatically populates the Next-Test-Proxy-Port header with the ephemeral port of the proxy daemon and the Next-Test-Data header with the supplied test data string before executing the underlying fetch request.
Sources: packages/next/src/experimental/testmode/proxy/server.ts:73-78