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 Client Segment Cache is a specialized client-side data management system in Next.js designed to store and serve pre-fetched React Server Component (RSC) route trees and individual page segments efficiently. Its primary role in the broader system is to accelerate client-side transitions and support Partial Prerendering (PPR) by maintaining granular cache entries that can be selectively queried, composed, and updated without blocking navigation. It solves the performance and bandwidth problems of traditional full-page prefetches by breaking down page responses into hierarchical segment units and matching them against dynamic request parameters. Key design decisions include synchronous cache lookups using multi-key paths, bounded memory consumption enforced by an LRU eviction strategy, and prioritized background prefetch scheduling. The segment cache integrates closely with adjacent components such as link visibility observers, router reducers, server action revalidations, and back-forward cache management to synchronize client navigation states with server-rendered updates. Sources: packages/next/src/client/components/segment-cache/cache.ts:1-137, packages/next/src/client/components/segment-cache/scheduler.ts:62-166, packages/next/src/client/components/segment-cache/cache-map.ts:1-94, packages/next/src/client/components/segment-cache/lru.ts:1-54, packages/next/src/client/components/segment-cache/vary-path.ts:1-50
The cache architecture relies on specialized multi-key map data structures and strict synchronous access patterns. Most asynchronous operations in the prefetch cache avoid async/await and instead spawn subtasks that write results to cache entries, attaching ping listeners to notify the prefetch queue. This allows synchronous traversal of data structures and immediate snapshots of the cache during synchronous updates, avoiding race conditions in a mutable cache. Sources: packages/next/src/client/components/segment-cache/cache.ts:124-135
The underlying storage mechanism is a specialized multi-key map where keys are tuples called keypaths. Each element of a keypath represents an input contributing to the entry value, such as a URL and parameters listed by the Vary header. The cache map supports a special Fallback key: when an exact match for a keypath is absent, the cache checks for a Fallback match. Because values exist at only a single keypath at a time, successive lookups are optimized by caching the internal map entry directly on the value via its ref field, skipping $O(n^2)$ fallback traversals. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:5-54
Values stored in the map must implement the MapValue protocol, tracking references, size, expiration timestamps, cache versions, and entry statuses. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:87-93
Sources: packages/next/src/client/components/segment-cache/cache-map.ts:87-93
Entry statuses are tracked via the EntryStatus enumeration. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:76-81
Sources: packages/next/src/client/components/segment-cache/cache-map.ts:76-81
Note
Each element of a keypath may have a Fallback, making cache retrieval an $O(n^2)$ operation in the worst case, though keypaths are expected to remain short. Values cannot be stored at multiple keypaths simultaneously; overlapping cases must be expressed using Fallback keys. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:28-48
Retrieving items from the cache map involves recursive matching with fallback handling and lazy expiration checks. The call sequence for reading an entry is getFromCacheMap() → getEntryWithFallbackImpl() → lazilyEvictIfNeeded() → isValueExpired(). Sources: packages/next/src/client/components/segment-cache/cache-map.ts:229-288
getFromCacheMap() initiates the lookup by passing parameters to getEntryWithFallbackImpl(). If a valid entry is found, it updates LRU positioning via lruPut() and returns entry.value. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:229-258getEntryWithFallbackImpl() traverses keypath elements recursively. For each level, it checks map.get(key) for an exact match. If no exact match exists, it falls back to map.get(Fallback). Sources: packages/next/src/client/components/segment-cache/cache-map.ts:300-370lazilyEvictIfNeeded() invokes isValueExpired(). If value.staleAt <= now or value.version < currentCacheVersion, deleteMapEntry() evicts the entry immediately and returns null. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:260-288When writing values, setInCacheMap() executes getOrInitialize() to locate or build the keypath node, invokes setMapEntryValue() to re-link references and update LRU sizes, and calls lruPut() to promote the entry to the front of the LRU list. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:374-389
Sources: packages/next/src/client/components/segment-cache/cache-map.ts:28-54, packages/next/src/client/components/segment-cache/cache-map.ts:268-288
Warning
During navigation lookups using readSegmentCacheEntryForNavigation, the cache performs up to two lookups: first an onlyMatchFulfilled pass that skips Pending or Rejected entries at more specific keypaths to find a cached shell fallback, followed by a regular fallback lookup if no fulfilled entry is found. Sources: packages/next/src/client/components/segment-cache/cache.ts:503-527
Vary paths represent linked lists of parameters and structural identifiers that govern how cache entries are reused across distinct URL states. Each vary path node specifies an id (such as a path parameter name or '?' for search parameters), a concrete or wildcard value, an optional isRootParam boolean indicator, and a parent pointer. Because route matching requires strict positional consistency, vary paths are constructed as pure functions of a segment's position within a route tree and the post-rewrite query URL. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:12-49
The client segment cache defines distinct vary path structures depending on whether a query targets an entire route, a layout segment, or a page segment. Route vary paths chain a pathname, a search string, and an optional Next-URL header. Segment vary paths bind a segment request key with nested parent path parameters or rendered search parameters. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:56-97
When a server response fulfills a segment or route request, vary paths are re-keyed to reflect exact parameter dependencies reported by the server or derived from interception rules. Unused parameters are replaced with the Fallback constant, allowing entries to serve subsequent requests with different parameter values. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:125-148, packages/next/src/client/components/segment-cache/vary-path.ts:355-383
Sources: packages/next/src/client/components/segment-cache/vary-path.ts:56-97
Note
The metadata "segment" is not a physical segment within the route tree, but it behaves like a page segment during caching. Because page request keys lack path information, metadata vary paths append HEAD_REQUEST_KEY to a simulated request key derived from the first parallel page segment to ensure proper separation in the client cache. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:210-253
Search parameters are exclusive to page segments and metadata. When determining how to access or store segment data for a request, getSegmentVaryPathForRequest() inspects the active FetchStrategy and the route tree configuration. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:255-325
FetchStrategy.RuntimeShell: Returns tree.shellVaryPath, substituting all non-root parameters and search parameters with Fallback while preserving root parameters and structural keys. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:282-288, packages/next/src/client/components/segment-cache/vary-path.ts:385-407fetchStrategy excludes search params (i.e., neither FetchStrategy.Full nor FetchStrategy.PPRRuntime), the search parameter node in the page vary path is patched with Fallback. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:293-320renderedSearch) within the vary path node. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:297-300, packages/next/src/client/components/segment-cache/vary-path.ts:323-324Sources: packages/next/src/client/components/segment-cache/vary-path.ts:255-325
Tip
Use clonePageVaryPathWithNewSearchParams() to dynamically retarget an existing PageVaryPath with a new normalized search string without rebuilding the entire path structure from the root tree. Sources: packages/next/src/client/components/segment-cache/vary-path.ts:327-344
The segment cache implements an in-memory Least Recently Used (LRU) doubly-linked list for tracking memory consumption across disparate value types such as route cache entries, segment cache entries, and back-forward cache entries. The cache maintains a soft memory ceiling configured by maxLruSize, defaulting to 50 MB. Sources: packages/next/src/client/components/segment-cache/lru.ts:5-15, packages/next/src/client/components/segment-cache/cache-map.ts:83-86
Memory tracking relies on three foundational functions exposed by the LRU module: lruPut(), updateLruSize(), and deleteFromLru(). When an entry is accessed or inserted, it moves to the front of the list using lruPut(). Sources: packages/next/src/client/components/segment-cache/lru.ts:16-54
export function lruPut(node: UnknownMapEntry) {
if (head === node) {
return
}
const prev = node.prev
const next = node.next
if (next === null || prev === null
Sources: packages/next/src/client/components/segment-cache/lru.ts:16-53
The call-chain execution walkthrough for updating or inserting an entry follows a precise order:
setInCacheMap() or getFromCacheMap() invokes lruPut(entry) upon accessing or inserting a node. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:255-257, packages/next/src/client/components/segment-cache/cache-map.ts:386-388lruPut() inspects whether node is already linked (next !== null && prev !== null). Sources: packages/next/src/client/components/segment-cache/lru.ts:16-23lruSize by node.size and calls ensureCleanupIsScheduled(). Sources: packages/next/src/client/components/segment-cache/lru.ts:23-29ensureCleanupIsScheduled() compares lruSize against maxLruSize; if the limit is exceeded, it triggers pingPrefetchScheduler(). Sources: packages/next/src/client/components/segment-cache/lru.ts:98-107Entries can change size independently of position movements. The updateLruSize() function isolates resizing operations, updating lruSize only if the node is actively tracked by the LRU list. Sources: packages/next/src/client/components/segment-cache/lru.ts:55-67
export function updateLruSize(node: UnknownMapEntry, newNodeSize: number) {
const prevNodeSize = node.size
node.size = newNodeSize
if (node.next === null) {
return
}
lruSize = lruSize - prevNodeSize + newNodeSize
Sources: packages/next/src/client/components/segment-cache/lru.ts:55-67
Warning
Entries exceeding the LRU size limit are not evicted immediately during mutation. Instead, cleanup is deferred to an asynchronous task by pinging the prefetch scheduler, which executes cleanup() once active prefetch queues and in-progress requests drain. Sources: packages/next/src/client/components/segment-cache/lru.ts:26-29, packages/next/src/client/components/segment-cache/lru.ts:98-107
When cleanup() runs, it continues evicting items from the tail of the LRU list until total memory usage drops to or below 90% of maxLruSize. Sources: packages/next/src/client/components/segment-cache/lru.ts:109-127
export function cleanup() {
if (lruSize <= maxLruSize) {
return
}
const ninetyPercentMax = maxLruSize * 0.9
while (lruSize > ninetyPercentMax && head !== null) {
const tail = head.prev
if (tail !== null
Sources: packages/next/src/client/components/segment-cache/lru.ts:109-127
Note
Read path lookups also perform lazy validation: lazilyEvictIfNeeded() checks whether a matched entry's value has expired via isValueExpired(). If expired, it calls deleteMapEntry(entry) immediately during the read and returns a cache miss. Sources: packages/next/src/client/components/segment-cache/cache-map.ts:260-288
Sources: packages/next/src/client/components/segment-cache/lru.ts:16-127, packages/next/src/client/components/segment-cache/cache-map.ts:268-298
Prefetch tasks are organized and prioritized using a min-heap scheduler backed by taskHeap. Tasks are processed in distinct phases to ensure that high-leverage structural work runs before per-link segment prefetching. The phases are evaluated via the PrefetchPhase enum: RouteTree fetches the route's tree structure, Shell fetches the reusable App Shell (param-free loading state) bounded by filesystem-route counts rather than link counts, and Speculative fetches concrete per-link segment data. Sources: packages/next/src/client/components/segment-cache/scheduler.ts:168-202
Sources: packages/next/src/client/components/segment-cache/scheduler.ts:168-202
New prefetch tasks are initiated via schedulePrefetchTask() or managed via link components through onLinkVisibilityChanged() and onNavigationIntent(). When a link enters the viewport via an IntersectionObserver, onLinkVisibilityChanged() sets instance.isVisible = true, adds the instance to prefetchableAndVisible, and reschedules its prefetch task with PrefetchPriority.Default. Hovering or touching a link triggers onNavigationIntent(), which bumps the task priority to PrefetchPriority.Intent and potentially upgrades the fetch strategy to FetchStrategy.Full if __NEXT_DYNAMIC_ON_HOVER is enabled. Sources: packages/next/src/client/components/segment-cache/scheduler.ts:271-313, packages/next/src/client/components/links.ts:259-300
Note
The scheduler reserves special network bandwidth for the most recently hovered or touched link (mostRecentlyHoveredLink), ensuring that intent-driven prefetches are not starved by background viewport tasks. Sources: packages/next/src/client/components/segment-cache/scheduler.ts:224-228
Bandwidth and request pacing are regulated by tracking active network operations (inProgressRequests) and enforcing revalidation cooldowns. When server action revalidations occur, startRevalidationCooldown() initiates a 300ms cooldown period (REVALIDATION_COOLDOWN_MS) during which prefetch requests are blocked to allow CDN cache propagation before retrying the prefetch queue via pingPrefetchScheduler(). Sources: packages/next/src/client/components/segment-cache/scheduler.ts:219-254
Client navigation read paths and Partial Prerendering (PPR) hydration coordinate through segment cache lookups, route tree diffing, and CacheNode assembly. When a navigation is initiated, the router checks existing cache entries using lookup helpers like readSegmentCacheEntryForNavigation(). This function performs up to two lookups: first searching for a fulfilled fallback entry at more-specific keypaths, and if none is found, falling back to a regular lookup to return the most specific match regardless of status. Sources: packages/next/src/client/components/segment-cache/cache.ts:503-527
To transition between routes, the router compares incoming segments against existing ones using compareSegments(), which classifies the relationship into distinct match variants. Reused shared cache nodes carry forward their scrollRef to preserve scroll intent across tree rebuilds and retain bfcacheId values so shared-layout segments keep a stable identity across navigations. Sources: packages/next/src/client/components/router-reducer/ppr-navigations.ts:894-911, packages/next/src/client/components/router-reducer/ppr-navigations.ts:1326-1342
Sources: packages/next/src/client/components/router-reducer/ppr-navigations.ts:1312-1342
Warning
Two successive route tree mismatches trigger a fallback to an MPA navigation to prevent infinite retry loops when server redirects or rewrites invalidate optimistic route predictions. Sources: packages/next/src/client/components/router-reducer/ppr-navigations.ts:1344-1347
Cache nodes are assembled via createCacheNode(), combining server-rendered React nodes, prefetch React payloads, head data, prefetch head data, and back-forward cache identifiers. During rendering, InnerLayoutRouter and associated boundary handlers iterate over router back-forward cache entries (RouterBFCacheEntry), wrapping each node in Activity boundaries with visibility modes determined by state key equality against the active state key. Sources: packages/next/src/client/components/router-reducer/ppr-navigations.ts:1279-1296, packages/next/src/client/components/layout-router.tsx:688-693, packages/next/src/client/components/layout-router.tsx:842-852
Note
Server-side rendering and initial client-side hydration trees use a fixed sentinel bfcacheId of 0 to reconcile cleanly across hydration, whereas subsequent client-side navigations increment a globally unique counter via generateBFCacheId(). Sources: packages/next/src/client/components/router-reducer/ppr-navigations.ts:1301-1310
Cache invalidation and lifecycle synchronization ensure that stale prefetches and expired back-forward cache entries do not pollute client navigations after server mutations. When server actions trigger revalidations, the caching layer coordinates cache evictions, CDN propagation delays, and version increments across both route and segment caches. Sources: packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:343-368, packages/next/src/client/components/segment-cache/cache.ts:424-441
When a server action executes and returns an action revalidation header indicating that data has changed, serverActionReducer() drives the invalidation sequence. Sources: packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:343-368
The invalidation call chain proceeds as follows: serverActionReducer() evaluates revalidationKind → calls invalidateBfCache() to increment the back-forward cache version → evaluates whether revalidationKind === ActionDidRevalidateStaticAndDynamic to invoke invalidateEntirePrefetchCache(nextUrl, state.tree) → invokes startRevalidationCooldown() to delay subsequent prefetches. Sources: packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:343-368, packages/next/src/client/components/segment-cache/bfcache.ts:63-68
Caution
If a server action triggers both static and dynamic revalidation (ActionDidRevalidateStaticAndDynamic), the entire prefetch cache is purged via invalidateEntirePrefetchCache(), discarding all active segment and route entries. Sources: packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts:361-363
The back-forward cache (bfcache) stores completed navigation payloads in a specialized CacheMap<BFCacheEntry> managed by bfcache.ts. Stale times are calculated relative to absolute timestamps. The helper function computeDynamicStaleAt(now, dynamicStaleTimeSeconds) converts server-sent dynamic stale times into absolute timestamps, falling back to the global DYNAMIC_STALETIME_MS constant when UnknownDynamicStaleTime (-1) is received. Sources: packages/next/src/client/components/segment-cache/bfcache.ts:15-22, packages/next/src/client/components/segment-cache/bfcache.ts:59-60
Similarly, getStaleTimeMs(staleTimeSeconds) enforces a strict lower bound of 30 seconds on stale times to prevent excessively short-lived configurations from disabling prefetching entirely. Sources: packages/next/src/client/components/segment-cache/cache.ts:120-122
Sources: packages/next/src/client/components/segment-cache/cache.ts:120-122, packages/next/src/client/components/segment-cache/bfcache.ts:15-68, packages/next/src/client/components/segment-cache/scheduler.ts:230-254
To accommodate propagation delays in CDN layers following a cache revalidation, startRevalidationCooldown() schedules a 300-millisecond timeout (REVALIDATION_COOLDOWN_MS). During this window, prefetch scheduling is suppressed. If multiple revalidations occur in rapid succession, existing timeout handles are cleared and reset via clearTimeout(), ensuring the cooldown period extends cleanly from the final invalidation event before calling pingPrefetchScheduler() to resume queued tasks. Sources: packages/next/src/client/components/segment-cache/scheduler.ts:230-254
staleAt | number | Absolute timestamp in milliseconds when the entry becomes stale. | packages/next/src/client/components/segment-cache/cache-map.ts:90 |
version | number | Cache version number used for global cache invalidation checks. | packages/next/src/client/components/segment-cache/cache-map.ts:91 |
status | EntryStatus | Lifecycle phase of the entry (Empty, Pending, Fulfilled, or Rejected). | packages/next/src/client/components/segment-cache/cache-map.ts:92 |
EntryStatus.Fulfilled | 2 | Data successfully received and parsed. | packages/next/src/client/components/segment-cache/cache-map.ts:79 |
EntryStatus.Rejected | 3 | Request failed with an error response. | packages/next/src/client/components/segment-cache/cache-map.ts:80 |
| Lazy Expiration Checks |
| Avoids expensive background sweeping timers by validating stamps on read. |
| Expired entries linger in memory until accessed or evicted by LRU capacity limits. |
| packages/next/src/client/components/segment-cache/cache-map.ts:268-288 |
pathParamsPartialSegmentVaryPath| Caches layout segments across nested dynamic path parameters. |
| packages/next/src/client/components/segment-cache/vary-path.ts:74-81 |
PageVaryPath | requestKey $\rightarrow$ searchParams (?) $\rightarrow$ pathParams | Caches page segments (and metadata) incorporating search parameters alongside path parameters. | packages/next/src/client/components/segment-cache/vary-path.ts:83-95 |
deleteFromLru | deleted: UnknownMapEntry | Unlinks a node from the LRU doubly-linked list and decrements lruSize. | packages/next/src/client/components/segment-cache/lru.ts:69-96 |
cleanup | None | Evicts tail entries asynchronously until LRU memory usage falls to 90% capacity. | packages/next/src/client/components/segment-cache/lru.ts:109-127 |
lazilyEvictIfNeeded | now: number, currentCacheVersion: number, entry: MapEntry<V>, onlyMatchFulfilled: boolean | Evaluates expiration during read lookups, evicting stale entries on-the-fly. | packages/next/src/client/components/segment-cache/cache-map.ts:268-298 |
Speculative| Highest phase number |
| Fetches concrete per-link segment data. |
| packages/next/src/client/components/segment-cache/scheduler.ts:199-200 |
CacheNodebfcacheId| packages/next/src/client/components/router-reducer/ppr-navigations.ts:1333-1340 |
Change | Default fallback case | Segments differ in routing structure; the CacheNode must be created fresh. | packages/next/src/client/components/router-reducer/ppr-navigations.ts:1341-1341 |
| packages/next/src/client/components/segment-cache/bfcache.ts:15-22 |
invalidateBfCache() | Increments currentBfCacheVersion | Invalidates all existing back-forward cache entries on the window object. | packages/next/src/client/components/segment-cache/bfcache.ts:63-68 |
startRevalidationCooldown() | REVALIDATION_COOLDOWN_MS = 300 | Blocks prefetch requests temporarily to allow CDN cache propagation. | packages/next/src/client/components/segment-cache/scheduler.ts:230-254 |