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:
The Response Cache subsystem is a server-level architectural component responsible for coordinating, batching, keying, and retaining rendered application payloads and HTML results across requests. In Next.js rendering pipelines (spanning both Pages and App Routers), multiple concurrent requests for the same route can arrive simultaneously. Without centralized control, this concurrency triggers redundant renders, race conditions, and excessive memory utilization. The ResponseCache class and its accompanying request-meta utilities solve this by intercepting lookups, wrapping generation callbacks inside specialized batchers (Batcher.create), and integrating with underlying incremental persistence layers (IncrementalCache or filesystem caches).
A core design decision in the response cache architecture is the decoupling of cache key generation from raw pathname strings through compound keys. By combining pathnames with invocation identifiers (invocationID) or fallback TTL sentinels (TTL_SENTINEL), the subsystem prevents conflicting overlapping renders in minimal server modes or on-demand revalidation tasks. Furthermore, the subsystem manages execution flow between volatile memory (LRUCache) and persistent backends, ensuring that background revalidations (waitUntil) do not block incoming client responses while maintaining strict cache integrity.
Sources: packages/next/src/server/response-cache/index.ts:53-104, packages/next/src/server/response-cache/index.ts:138-190
The subsystem interacts tightly with RouteModule dispatchers, RenderResult abstractions, and asynchronous storage contexts (workAsyncStorage, workUnitAsyncStorage). By standardizing how cache entries are parsed, converted (toResponseCacheEntry, fromResponseCacheEntry), and protected against stampedes, the response cache provides a reliable bridge between dynamic server-side rendering logic and static distribution targets.
Sources: packages/next/src/server/response-cache/utils.ts:14-40, packages/next/src/server/route-modules/route-module.ts:1103-1173
The response cache subsystem exposes class-based interfaces for managing cached route responses and minimal-mode memory entries. The primary implementation is ResponseCache, adhering to the ResponseCacheBase contract, alongside a lighter WebResponseCache implementation for edge/web runtimes lacking persistent incremental cache bindings.
ResponseCache: Main server-side cache manager. Implements get() to coordinate lookup, request batching, and cache population.WebResponseCache: Lightweight map-based response cache used in web runtimes where incremental cache stores are unavailable.RenderResult: Encapsulates response payloads (strings, buffers, or readable streams) along with route metadata (headers, status codes, revalidation controls).Sources: packages/next/src/server/response-cache/web.ts:8-32, packages/next/src/server/render-result.ts:111-201
Sources: packages/next/src/server/response-cache/index.ts:200-218, packages/next/src/server/render-result.ts:199-201
Cache lookup keys in ResponseCache are not simple pathnames. To support minimal mode and background revalidation tasks without data contamination, the subsystem constructs compound keys joining the route pathname with an invocation identifier or fallback sentinel.
The compound key structure uses a null byte (\0) separator (KEY_SEPARATOR) because null bytes cannot appear in valid URL paths or UUIDs. When minimal_mode is enabled, entries are stored in a bounded LRUCache instance.
Sources: packages/next/src/server/response-cache/index.ts:59-91, packages/next/src/server/response-cache/index.ts:138-190
Note
When invocationID is undefined, the subsystem falls back to TTL_SENTINEL (__ttl_sentinel__) and validates entries against a configurable Time-To-Live (DEFAULT_TTL_MS, defaulting to 10 seconds). Memory pressure is managed via LRU eviction rather than active timers.
To prevent cache stampedes and duplicate render passes when multiple concurrent requests target an uncached route, ResponseCache utilizes two internal Batcher instances: getBatcher and revalidateBatcher.
private readonly getBatcher = Batcher.create<
{ key: string; isOnDemandRevalidate: boolean },
IncrementalResponseCacheEntry | null,
string
>({
cacheKeyFn: ({ key, isOnDemandRevalidate }) =>
`${key}-${isOnDemandRevalidate ? '1' : '0'}`,
schedulerFn: scheduleOnNextTick,
})When ResponseCache.get() is invoked:
key is null, it bypasses caching entirely and immediately executes the responseGenerator.toResponseCacheEntry and returned.getBatcher.batch(). The batcher ensures that subsequent lookups with identical keys during the current tick reuse the pending promise.waitUntil(promise) to ensure serverless containers or Node runtimes do not prematurely terminate execution.The following walkthrough traces the verified execution path from route handling through response cache lookup, conversion, static instantiation, and result wrapping (handleResponse → get → toResponseCacheEntry → fromStatic → RenderResult):
Sources: packages/next/src/server/route-modules/route-module.ts:1111-1171, packages/next/src/server/response-cache/index.ts:200-307, packages/next/src/server/response-cache/utils.ts:42-79, packages/next/src/server/render-result.ts:149-158, packages/next/src/server/render-result.ts:110-170
RouteModule.handleResponse() receives rendering options and calls responseCache.get(cacheKey, responseGenerator, context).ResponseCache.get() verifies keys, checks memory caches, or delegates to handleGet() via the batcher to load incremental cache payloads.toResponseCacheEntry() transforms stored incremental entries into ResponseCacheEntry structures.RenderResult.fromStatic() wraps static string or buffer payloads along with the HTML content type.RenderResult instance is returned to the handler for pipeline dispatch.Sources: packages/next/src/server/route-modules/route-module.ts:1138-1155, packages/next/src/server/response-cache/index.ts:306-307, packages/next/src/server/response-cache/utils.ts:42-79, packages/next/src/server/render-result.ts:149-170
Sources: packages/next/src/server/route-modules/route-module.ts:1138-1171, packages/next/src/server/response-cache/utils.ts:42-79, packages/next/src/server/render-result.ts:149-158
The response cache converts internal cache records between storage formats and runtime render results using conversion utility functions.
ResponseCacheEntry: Runtime representation containing RenderResult instances (html), headers, status codes, and CacheControl metadata.IncrementalResponseCacheEntry: Serialized representation where HTML and RSC payloads are stored as unchunked strings or binary buffers (Buffer).Caution
Dynamic responses cannot be unchunked synchronously. Attempting to call toUnchunkedString() on an active stream without specifying stream: true will throw an InvariantError.
The response cache subsystem implements specific guardrails for memory exhaustion, eviction monitoring, and missing cache entries:
LRUCache evicts an entry in minimal mode, the eviction listener extracts the invocationID from the compound key and registers it in evictedInvocationIDs (bounded to 100 entries). If a subsequent request matches an evicted invocation ID, warnOnce logs a warning advising the developer to increase NEXT_PRIVATE_RESPONSE_CACHE_MAX_SIZE.'invariant: cache entry required but not generated'.WebResponseCache, errors thrown during background revalidation are caught and logged if the response promise has already resolved, preventing unhandled rejections from crashing the worker.Sources: packages/next/src/server/response-cache/index.ts:145-190, packages/next/src/server/route-modules/route-module.ts:1157-1171, packages/next/src/server/response-cache/web.ts:114-126