---
title: "Client Segment Cache"
description: "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..."
last_updated: "2026-09-23T10:52:03.131764+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-routing/client-segment-cache"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/next/src/client/components/segment-cache/cache.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts)
- [packages/next/src/client/components/segment-cache/scheduler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts)
- [packages/next/src/server/use-cache/use-cache-wrapper.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts)
- [packages/next/src/server/app-render/collect-segment-data.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/collect-segment-data.tsx)
- [packages/next/src/server/app-render/app-render.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx)
- [packages/next/src/server/route-modules/route-module.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/route-module.ts)
- [packages/next/src/client/components/router-reducer/ppr-navigations.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts)
- [packages/next/src/server/app-render/instant-validation/instant-validation.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/instant-validation/instant-validation.tsx)
- [packages/next/src/client/components/segment-cache/lru.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts)
- [packages/next/src/client/components/segment-cache/cache-map.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts)
- [packages/next/src/client/components/layout-router.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/layout-router.tsx)
- [packages/next/src/server/dev/on-demand-entry-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts)
- [packages/next/src/client/components/segment-cache/bfcache.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts)
- [packages/next/src/client/components/segment-cache/vary-path.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts)
- [packages/next/src/server/lib/incremental-cache/memory-cache.external.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/incremental-cache/memory-cache.external.ts)
- [packages/next/src/client/components/links.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts)
- [packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts)
</details>

## Overview

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L1-L137), [packages/next/src/client/components/segment-cache/scheduler.ts:62-166](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L62-L166), [packages/next/src/client/components/segment-cache/cache-map.ts:1-94](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L1-L94), [packages/next/src/client/components/segment-cache/lru.ts:1-54](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L1-L54), [packages/next/src/client/components/segment-cache/vary-path.ts:1-50](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L1-L50)

## Cache Architecture and Map Storage

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L124-L135)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L5-L54)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L87-L93)

| Protocol Property | Type | Meaning | Sources |
| :--- | :--- | :--- | :--- |
| `ref` | `UnknownMapEntry \| null` | Direct pointer back to the containing map node for fast lookups. | [packages/next/src/client/components/segment-cache/cache-map.ts:88](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L88) |
| `size` | `number` | Memory size of the cache entry in bytes for LRU tracking. | [packages/next/src/client/components/segment-cache/cache-map.ts:89](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L89) |
| `staleAt` | `number` | Absolute timestamp in milliseconds when the entry becomes stale. | [packages/next/src/client/components/segment-cache/cache-map.ts:90](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L90) |
| `version` | `number` | Cache version number used for global cache invalidation checks. | [packages/next/src/client/components/segment-cache/cache-map.ts:91](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L91) |
| `status` | `EntryStatus` | Lifecycle phase of the entry (Empty, Pending, Fulfilled, or Rejected). | [packages/next/src/client/components/segment-cache/cache-map.ts:92](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L92) |

Sources: [packages/next/src/client/components/segment-cache/cache-map.ts:87-93](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L87-L93)

Entry statuses are tracked via the `EntryStatus` enumeration. Sources: [packages/next/src/client/components/segment-cache/cache-map.ts:76-81](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L76-L81)

| Status Constant | Numeric Value | Lifecycle Meaning | Sources |
| :--- | :--- | :--- | :--- |
| `EntryStatus.Empty` | `0` | No data present; detached or placeholder entry. | [packages/next/src/client/components/segment-cache/cache-map.ts:77](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L77) |
| `EntryStatus.Pending` | `1` | Request dispatched; waiting for server data. | [packages/next/src/client/components/segment-cache/cache-map.ts:78](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L78) |
| `EntryStatus.Fulfilled` | `2` | Data successfully received and parsed. | [packages/next/src/client/components/segment-cache/cache-map.ts:79](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L79) |
| `EntryStatus.Rejected` | `3` | Request failed with an error response. | [packages/next/src/client/components/segment-cache/cache-map.ts:80](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L80) |

Sources: [packages/next/src/client/components/segment-cache/cache-map.ts:76-81](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L76-L81)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L28-L48)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L229-L288)

1. `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-258](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L229-L258)
2. `getEntryWithFallbackImpl()` 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-370](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L300-L370)
3. When reaching the terminal node, `lazilyEvictIfNeeded()` 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-288](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L260-L288)

When 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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L374-L389)

| Design Choice | Benefit | Cost | Sources |
| :--- | :--- | :--- | :--- |
| **Tuple Keypaths with Fallback** | Allows partial parameter matching and route template reuse without full re-fetches. | Increases lookup complexity up to $O(n^2)$ when multiple fallback levels are evaluated. | [packages/next/src/client/components/segment-cache/cache-map.ts:28-33](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L28-L33) |
| **Direct Value `ref` Caching** | Bypasses recursive tree traversal on subsequent accesses to the same entry. | Requires maintaining bidirectional pointers between map entries and values during re-assignments. | [packages/next/src/client/components/segment-cache/cache-map.ts:49-54](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L49-L54) |
| **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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L268-L288) |

Sources: [packages/next/src/client/components/segment-cache/cache-map.ts:28-54](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L28-L54), [packages/next/src/client/components/segment-cache/cache-map.ts:268-288](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L268-L288)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L503-L527)

## Vary Paths and Parameter Resolution

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L12-L49)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L56-L97)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L125-L148), [packages/next/src/client/components/segment-cache/vary-path.ts:355-383](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L355-L383)

| Vary Path Type | Structure / Chain Order | Purpose | Sources |
| :--- | :--- | :--- | :--- |
| **`RouteVaryPath`** | `requestKey` $\rightarrow$ `searchParams` (`?`) $\rightarrow$ `nextUrl` | Identifies and caches top-level route lookups, considering URL search and Next-URL headers. | [packages/next/src/client/components/segment-cache/vary-path.ts:56-71](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L56-L71) |
| **`LayoutVaryPath`** | `requestKey` $\rightarrow$ `pathParams` (chained via `PartialSegmentVaryPath`) | Caches layout segments across nested dynamic path parameters. | [packages/next/src/client/components/segment-cache/vary-path.ts:74-81](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L74-L81) |
| **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L83-L95) |

Sources: [packages/next/src/client/components/segment-cache/vary-path.ts:56-97](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L56-L97)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L210-L253)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L255-L325)

- **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L282-L288), [packages/next/src/client/components/segment-cache/vary-path.ts:385-407](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L385-L407)
- **Static Prefetches**: Static strategies never vary on search parameters. If `fetchStrategy` 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-320](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L293-L320)
- **Runtime Full / PPRRuntime Prefetches**: Preserves the concrete search parameter value (`renderedSearch`) within the vary path node. Sources: [packages/next/src/client/components/segment-cache/vary-path.ts:297-300](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L297-L300), [packages/next/src/client/components/segment-cache/vary-path.ts:323-324](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L323-L324)

Sources: [packages/next/src/client/components/segment-cache/vary-path.ts:255-325](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L255-L325)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/vary-path.ts#L327-L344)

## LRU Eviction and Memory Management

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L5-L15), [packages/next/src/client/components/segment-cache/cache-map.ts:83-86](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L83-L86)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L16-L54)

```typescript
export function lruPut(node: UnknownMapEntry) {
  if (head === node) {
    return
  }
  const prev = node.prev
  const next = node.next
  if (next === null || prev === null) {
    lruSize += node.size
    ensureCleanupIsScheduled()
  } else {
    prev.next = next
    next.prev = prev
  }

  if (head === null) {
    node.prev = node
    node.next = node
  } else {
    const tail = head.prev
    node.prev = tail
    if (tail !== null) {
      tail.next = node
    }
    node.next = head
    head.prev = node
  }
  head = node
}
```

Sources: [packages/next/src/client/components/segment-cache/lru.ts:16-53](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L16-L53)

The call-chain execution walkthrough for updating or inserting an entry follows a precise order:
1. `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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L255-L257), [packages/next/src/client/components/segment-cache/cache-map.ts:386-388](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L386-L388)
2. `lruPut()` inspects whether `node` is already linked (`next !== null && prev !== null`). Sources: [packages/next/src/client/components/segment-cache/lru.ts:16-23](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L16-L23)
3. If unlinked (an insertion), it increments `lruSize` by `node.size` and calls `ensureCleanupIsScheduled()`. Sources: [packages/next/src/client/components/segment-cache/lru.ts:23-29](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L23-L29)
4. `ensureCleanupIsScheduled()` compares `lruSize` against `maxLruSize`; if the limit is exceeded, it triggers `pingPrefetchScheduler()`. Sources: [packages/next/src/client/components/segment-cache/lru.ts:98-107](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L98-L107)

Entries 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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L55-L67)

```typescript
export function updateLruSize(node: UnknownMapEntry, newNodeSize: number) {
  const prevNodeSize = node.size
  node.size = newNodeSize
  if (node.next === null) {
    return
  }
  lruSize = lruSize - prevNodeSize + newNodeSize
  ensureCleanupIsScheduled()
}
```

Sources: [packages/next/src/client/components/segment-cache/lru.ts:55-67](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L55-L67)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L26-L29), [packages/next/src/client/components/segment-cache/lru.ts:98-107](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L98-L107)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L109-L127)

```typescript
export function cleanup() {
  if (lruSize <= maxLruSize) {
    return
  }
  const ninetyPercentMax = maxLruSize * 0.9
  while (lruSize > ninetyPercentMax && head !== null) {
    const tail = head.prev
    if (tail !== null) {
      deleteMapEntry(tail)
    }
  }
}
```

Sources: [packages/next/src/client/components/segment-cache/lru.ts:109-127](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L109-L127)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L260-L288)

| Function Name | Parameters | Purpose | Sources |
| :--- | :--- | :--- | :--- |
| **`lruPut`** | `node: UnknownMapEntry` | Inserts or repositions a node to the head of the LRU doubly-linked list and tracks sizing. | [packages/next/src/client/components/segment-cache/lru.ts:16-53](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L16-L53) |
| **`updateLruSize`** | `node: UnknownMapEntry, newNodeSize: number` | Adjusts an entry's tracked memory footprint and schedules cleanup if capacity is exceeded. | [packages/next/src/client/components/segment-cache/lru.ts:55-67](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L55-L67) |
| **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L69-L96) |
| **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L109-L127) |
| **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L268-L298) |

Sources: [packages/next/src/client/components/segment-cache/lru.ts:16-127](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/lru.ts#L16-L127), [packages/next/src/client/components/segment-cache/cache-map.ts:268-298](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache-map.ts#L268-L298)

## Prefetch Task Priority and Scheduling

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L168-L202)

| Phase Name | Value / Order | Purpose | Sources |
| :--- | :--- | :--- | :--- |
| **`RouteTree`** | Lowest priority phase | Fetches the route's initial tree structure. | [packages/next/src/client/components/segment-cache/scheduler.ts:192-194](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L192-L194) |
| **`Shell`** | Intermediate phase | Fetches the reusable App Shell (param-free loading state) for routes supporting PPR. | [packages/next/src/client/components/segment-cache/scheduler.ts:195-198](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L195-L198) |
| **`Speculative`** | Highest phase number | Fetches concrete per-link segment data. | [packages/next/src/client/components/segment-cache/scheduler.ts:199-200](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L199-L200) |

Sources: [packages/next/src/client/components/segment-cache/scheduler.ts:168-202](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L168-L202)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L271-L313), [packages/next/src/client/components/links.ts:259-300](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L259-L300)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L224-L228)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L219-L254)

## Navigation Read Path and PPR Hydration

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L503-L527)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L894-L911), [packages/next/src/client/components/router-reducer/ppr-navigations.ts:1326-1342](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1326-L1342)

| Segment Match Kind | Condition | Meaning | Sources |
| :--- | :--- | :--- | :--- |
| **`Match`** | `matchSegment(newSegment, oldSegment)` returns true | Two segments are equivalent; the `CacheNode` can be reused as-is. | [packages/next/src/client/components/router-reducer/ppr-navigations.ts:1330-1332](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1330-L1332) |
| **`SearchParamOnlyChange`** | Both segments are page strings starting with `PAGE_SEGMENT_KEY` but fail structural match | Page segments differ only in search params; the `CacheNode` is rebuilt while carrying forward the `bfcacheId`. | [packages/next/src/client/components/router-reducer/ppr-navigations.ts:1333-1340](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1333-L1340) |
| **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1341-L1341) |

Sources: [packages/next/src/client/components/router-reducer/ppr-navigations.ts:1312-1342](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1312-L1342)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1344-L1347)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1279-L1296), [packages/next/src/client/components/layout-router.tsx:688-693](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/layout-router.tsx#L688-L693), [packages/next/src/client/components/layout-router.tsx:842-852](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/layout-router.tsx#L842-L852)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L1301-L1310)

## Cache Invalidation and Lifecycle Synchronization

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L343-L368), [packages/next/src/client/components/segment-cache/cache.ts:424-441](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L424-L441)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L343-L368)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L343-L368), [packages/next/src/client/components/segment-cache/bfcache.ts:63-68](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L63-L68)

> [!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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L361-L363)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L15-L22), [packages/next/src/client/components/segment-cache/bfcache.ts:59-60](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L59-L60)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L120-L122)

| Stale / Invalidation Function | Default Value / Sentinel | Purpose | Sources |
| :--- | :--- | :--- | :--- |
| **`getStaleTimeMs()`** | `Math.max(staleTimeSeconds, 30) * 1000` | Enforces a minimum 30-second stale time floor for segment prefetches. | [packages/next/src/client/components/segment-cache/cache.ts:120-122](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L120-L122) |
| **`computeDynamicStaleAt()`** | `UnknownDynamicStaleTime` (`-1`) | Converts dynamic stale time seconds into an absolute `staleAt` timestamp. | [packages/next/src/client/components/segment-cache/bfcache.ts:15-22](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L15-L22) |
| **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L63-L68) |
| **`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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L230-L254) |

Sources: [packages/next/src/client/components/segment-cache/cache.ts:120-122](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L120-L122), [packages/next/src/client/components/segment-cache/bfcache.ts:15-68](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L15-L68), [packages/next/src/client/components/segment-cache/scheduler.ts:230-254](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L230-L254)

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](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L230-L254)

## Related

- [Prefetching and PPR](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-routing/prefetching-and-ppr)
- [Router State Reducer](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-routing/router-state-reducer)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
