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 Incremental Cache powers Next.js rendering optimization and incremental static regeneration (ISR) by persisting and retrieving prerendered routes, data fetches, and function outputs across requests. It bridges server-side render pipelines and persistent backends—such as the default file system cache or custom cache handlers—to eliminate redundant computation and database queries. By integrating request deduplication, cache tag invalidation, and client segment coordination, the incremental cache subsystem ensures data freshness while maintaining high-performance response streaming.
Sources: packages/next/src/server/lib/incremental-cache/index.ts:83-95, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:36-63
The IncrementalCache subsystem manages persistent caching across Next.js rendering operations by implementing the IncrementalCache and CacheHandler interfaces. It coordinates between in-memory caches, disk storage via FileSystemCache, and custom pluggable cache handlers. When a cache lookup is requested, the system inspects route metadata, file modification times (mtime), and tag expiration states to determine whether a stored entry is fresh, stale, or expired.
Sources: packages/next/src/server/lib/incremental-cache/index.ts:39-81, packages/next/src/server/lib/incremental-cache/index.ts:83-105, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:36-63
The cache architecture relies on explicit TypeScript interfaces that define the contract for retrieving, storing, and revalidating cache entries across different cache kinds.
Sources: packages/next/src/server/lib/incremental-cache/index.ts:39-81, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:36-63
The FileSystemCache manages multi-format assets on disk, distinguishing between APP_ROUTE, APP_PAGE, PAGES, and FETCH cache kinds. When retrieving items, it checks the LRU memory cache before falling back to file system reads via this.fs.readFile and this.fs.stat.
Sources: packages/next/src/server/lib/incremental-cache/file-system-cache.ts:105-120, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:121-296
Sources: packages/next/src/server/lib/incremental-cache/index.ts:133-162, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:50-63, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:121-256
Warning
When flushToDisk is disabled or running in edge runtime (NEXT_RUNTIME === 'edge'), FileSystemCache bypasses disk reads for fetch caches or limits persistence, relying entirely on memory or throwing invariant errors if unexpected route kinds are encountered.
Sources: packages/next/src/server/lib/incremental-cache/file-system-cache.ts:120-121, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:157-158, packages/next/src/server/lib/incremental-cache/file-system-cache.ts:283-287
The response cache manages in-memory caching and request deduplication for generated responses. It relies on an LRU storage engine configured via environment variables and uses a batcher utility to ensure that concurrent, identical render requests share a single inflight operation.
Sources: packages/next/src/server/response-cache/index.ts:9-12, packages/next/src/server/response-cache/index.ts:53-56
In-memory caching behavior is governed by tunable constants parsed from environment variables. These parameters control default Time-To-Live (TTL) values and maximum storage sizes.
Note
Compound cache keys combine pathnames and invocation identifiers separated by a null byte (\0), ensuring isolation across distinct render invocations. When an invocation identifier is missing, a reserved __ttl_sentinel__ marker is used.
When multiple concurrent callers request the same cache key, duplicate render executions are prevented using a batching mechanism. The call chain routes requests through the cache lookup layer and coordinates revalidations:
handleGet() → revalidate() → revalidateBatcher.batch() → handleRevalidate() → responseGenerator()
Sources: packages/next/src/server/response-cache/index.ts:318-331, packages/next/src/server/response-cache/index.ts:418-427, packages/next/src/server/response-cache/index.ts:428-444, packages/next/src/server/response-cache/index.ts:446-461
Next.js intercepts and patches the global fetch API during route execution to integrate automatic request deduplication and incremental caching. Global fetch monkey-patching ensures that standard fetch() calls executed within server components, route handlers, or page components flow through specialized wrappers capable of tracking metrics, enforcing cache rules, and sharing inflight promises across concurrent calls.
Sources: packages/next/src/server/lib/patch-fetch.ts:427-430, packages/next/src/server/route-modules/app-route/module.ts:22-22
The deduplication wrapper (dedupeFetch) handles incoming request arguments by normalizing string URLs or Request instances into a structured cache key via generateCacheKey. Requests possessing side effects (such as POST, PUT, DELETE, or PATCH methods, or those with keepalive enabled) bypass deduplication and execute directly against the original fetch.
Note
Passing an explicit AbortSignal via options.signal acts as an opt-out mechanism for request deduplication, forcing dedupeFetch to skip the in-memory cache layer and execute originalFetch immediately.
The cache tag invalidation pipeline manages on-demand purging of cached data and incremental static regeneration (ISR) state through tag encoding, revalidation triggering, and tags manifest synchronization. When developers invalidate cached data via user-facing functions such as revalidateTag, updateTag, or revalidatePath, inputs are processed and normalized to ensure wire and storage consistency before being dispatched through asynchronous execution boundaries.
Sources: packages/next/src/server/web/spec-extension/revalidate.ts:34-41, packages/next/src/server/web/spec-extension/revalidate.ts:49-63, packages/next/src/server/web/spec-extension/revalidate.ts:97-123
To prevent Node.js header validation errors (such as ERR_INVALID_CHAR) when tag names or route paths contain non-ASCII characters or run-time unicode symbols, tag inputs pass through encodeCacheTag. This function evaluates strings against printable ASCII rules and percent-encodes out-of-class character runs while preserving structural separators like commas, forward slashes, and dynamic segment markers.
Path-based revalidations handled by revalidatePath normalize paths by removing trailing slashes, attaching implicit tag prefixes (NEXT_CACHE_IMPLICIT_TAG_ID), and appending layout or page types. If a normalized path points to a root or index location, sibling variants are automatically added to the tag array to keep cache references synchronized.
Sources: packages/next/src/server/web/spec-extension/revalidate.ts:34-90, packages/next/src/server/web/spec-extension/revalidate.ts:97-123
When a revalidation function is invoked, it validates work store state and checks the active workUnitStore phase. Calling revalidation methods inside a render phase, cache wrapper, or generateStaticParams throws an immediate error or triggers runtime suspension. Valid tags are pushed into store.pendingRevalidatedTags and processed via runtime helper wrappers.
The revalidation flow follows a structured execution sequence managed by lifecycle wrapper routines:
withExecuteRevalidates() → cloneRevalidationState() → callback() → diffRevalidationState() → executeRevalidates() → revalidateTags()
Sources: packages/next/src/server/revalidation-utils.ts:6-26, packages/next/src/server/revalidation-utils.ts:186-221
Warning
Invoking revalidateTag, updateTag, or revalidatePath directly during a React component render or inside a use cache body will throw an error. Revalidation must always execute outside of renders and cached functions to ensure consistency.
The tagsManifest map shares state between "use cache" handlers and file-system caches using TagManifestEntry definitions. During cache evaluation, areTagsExpired and areTagsStale inspect manifest values against requested timestamps.
export interface TagManifestEntry {
stale?: number
expired?: number
}
export const tagsManifest = new Map<string, TagManifestEntry>()For immediate expiration checks, areTagsExpired calculates current performance times (performance.timeOrigin + performance.now()) to determine whether an entry's expiration threshold has elapsed relative to the target cache timestamp. Similarly, areTagsStale iterates through tag arrays to verify if recorded stale thresholds exceed baseline generation timestamps.
Next.js caches and wraps expensive operations, database queries, and component trees through unstable_cache and the 'use cache' directive infrastructure. These wrappers manage execution lifecycles, propagate cache metadata across asynchronous storage boundaries, handle cache invalidation conditions, and enforce timeout or revalidation rules.
When an unstable_cache-wrapped function is invoked, it passes through a deterministic sequence of async storage lookups, cache key generation, store configuration, and cache validation checks.
The execution flow proceeds as follows:
unstable_cache invocation (cachedCb) → workAsyncStorage.getStore() / workUnitAsyncStorage.getStore() → incrementalCache.generateCacheKey() → incrementalCache.get() → cacheEntry.isStale check (background revalidation vs. foreground blocking revalidation) → execution via workUnitAsyncStorage.run(innerCacheStore, cb, ...args) → cacheNewResult() or direct return.
Warning
Passing an explicit revalidate: 0 option to unstable_cache() throws an immediate invariant error at construction time. Revalidation values must be explicitly set to false or a positive number greater than zero (> 0).
The cacheTag() utility registers custom tags against the active execution context. It enforces structural checks via workUnitAsyncStorage, ensuring that tags are only declared within valid cache scopes and throwing descriptive errors if called incorrectly.
export function cacheTag(...tags: string[]): void {
if (!process.env.__NEXT_USE_CACHE) {
throw new Error(
'`cacheTag()` is only available with the `cacheComponents` config.'
)
}
const workUnitStore = workUnitAsyncStorage.getStore()
switch (workUnitStore?.type) {
case 'prerender':
case 'prerender-client':
case 'validation-client':
case 'prerender-runtime':
case 'prerender-ppr':
case 'prerender-legacy':
case 'request':
case 'unstable-cache':
case 'generate-static-params':
case undefined:
throw new Error(
'`cacheTag()` can only be called inside a "use cache" function.'
)
case 'cache':
case 'private-cache':
break
default:
workUnitStore satisfies never
}
const validTags = validateTags(tags, '`cacheTag()`')
if (!workUnitStore.tags) {
workUnitStore.tags = validTags
} else {
workUnitStore.tags.push(...validTags)
}
}Note
Calling cacheTag() outside of a valid 'use cache' function context — such as during standard page requests or inside generateStaticParams — triggers an immediate error aborting execution.
When evaluating whether to serve or discard a cached entry, shouldDiscardCacheEntry and shouldForceRevalidate inspect implicit tags, recent revalidation flags, draft mode status, and work store headers.
Sources: packages/next/src/server/use-cache/use-cache-wrapper.ts:3286-3288, packages/next/src/server/use-cache/use-cache-wrapper.ts:3293-3293, packages/next/src/server/use-cache/use-cache-wrapper.ts:3323-3332, packages/next/src/server/use-cache/use-cache-wrapper.ts:3359-3367
The client segment cache coordinates how route segments, prefetch streams, and prerender hydration payloads are stored, decoded, and validated. Responses from the server carry specialized byte markers and metadata that dictate whether a stream is partial or complete before ingestion into the cache.
Sources: packages/next/src/client/components/segment-cache/cache.ts:3117-3127, packages/next/src/client/components/segment-cache/cache.ts:3215-3224
When runtime prefetch streams are received, processRuntimePrefetchStream strips leading control bytes using stripIsPartialByte, decodes the underlying React Server Components stream, and extracts vary parameters and stale times.
The byte marker prefix inspection follows a strict protocol:
Note
Valid RSC Flight rows start with a hex digit or a colon (:), ensuring that marker bytes (# or ~) never collide with legitimate Flight data rows.
On the server side, instant validation builds route trees and traverses payload segments to coordinate validation boundaries and segment request keys. The traversal visits each route node and formats segment path strings using URL encoding conventions.
Sources: packages/next/src/server/app-render/instant-validation/instant-validation.tsx:109-126, packages/next/src/server/app-render/instant-validation/instant-validation.tsx:182-188
function stringifySegment(segment: Segment): SegmentPath {
return (
typeof segment === 'string'
? encodeURIComponent(segment)
: encodeURIComponent(segment[0]) + '|' + segment[1] + '|' + segment[2]
) as SegmentPath
}Warning
Unmarked runtime prefetch responses behave differently depending on whether cached navigations are enabled globally; omitting the experimental flag changes response partiality defaults.