---
title: "Prefetching and PPR"
description: "Partial Prerendering (PPR) and prefetching form the architectural backbone of Next.js client-side navigation, optimizing application responsiveness by separating static shells from dynamic data str..."
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/prefetching-and-ppr"
---

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

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

- [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/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/shared/lib/router/router.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/router.ts)
- [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/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/client/components/segment-cache/navigation.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.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/route-loader.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/route-loader.ts)
- [packages/next/src/client/dev/debug-channel.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/debug-channel.ts)
- [packages/next/src/client/components/segment-cache/navigation-testing-lock.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation-testing-lock.ts)
- [packages/next/src/client/components/segment-cache/prefetch.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/prefetch.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/optimistic-routes.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts)
</details>

## Overview

Partial Prerendering (PPR) and prefetching form the architectural backbone of Next.js client-side navigation, optimizing application responsiveness by separating static shells from dynamic data streams. By combining viewport observation with priority heap task scheduling, Next.js proactively prefetches route trees and individual segment bundles before a user navigates, eliminating round-trip latency.
Sources: [packages/next/src/client/components/segment-cache/scheduler.ts:621-627](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L621-L627), [packages/next/src/client/components/links.ts:249-300](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L249-L300)

The Segment Cache orchestrates the storage, LRU retention, dynamic staleness computation, and mutation-driven invalidation of cached route elements. Optimistic routing exploits pattern discovery to match client route trees instantly, falling back to server resolution or rewrite handling when mismatches occur.
Sources: [packages/next/src/client/components/segment-cache/cache.ts:3081-3110](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L3081-L3110), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:1-44](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L1-L44)

During navigation, PPR executes immediate shell rendering alongside deferred dynamic RSC fetches, reconciling router trees via copy-on-write task updates and staged payloads. Integration with the browser's back-forward cache preserves session history and dynamic segment states, while synchronization locks and development debug channels secure navigation execution and diagnostic telemetry.
Sources: [packages/next/src/client/components/router-reducer/ppr-navigations.ts:163-190](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L163-L190), [packages/next/src/client/components/segment-cache/bfcache.ts:32-57](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L32-L57), [packages/next/src/client/dev/debug-channel.ts:307-359](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/debug-channel.ts#L307-L359)

## Link Prefetching and Task Scheduling

### Overview

Link prefetching and task scheduling manage when and how navigation targets are identified, observed for visibility, and prioritized for prefetching. By using a shared `IntersectionObserver` across all `<Link>` components with a root margin of `200px`, Next.js observes when anchor tags enter or approach the viewport. When visibility status updates or a navigation intent is triggered via user interaction, link instances coordinate with the segment cache scheduler to initiate or reschedule background prefetch tasks.
Sources: [packages/next/src/client/components/links.ts:107-141](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L107-L141), [packages/next/src/client/components/links.ts:249-300](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L249-L300)

### Viewport Observation and Trigger Mechanisms

Link instances are registered using `mountLinkInstance` or `mountFormInstance`, storing references inside a prefetchable collection tracked by an `IntersectionObserver`. The observation flow proceeds through specific function calls:
1. `handleIntersect()` receives entries from the observer and determines visibility via `entry.intersectionRatio > 0`.
2. `onLinkVisibilityChanged()` updates `instance.isVisible`, adds or removes the instance from `prefetchableAndVisible`, and calls `rescheduleLinkPrefetch()` with `PrefetchPriority.Default`.
3. `onNavigationIntent()` is invoked on hover or touch events, optionally upgrading the fetch strategy to `FetchStrategy.Full` when `__NEXT_DYNAMIC_ON_HOVER` and `unstable_upgradeToDynamicPrefetch` are enabled, and reschedules the prefetch with `PrefetchPriority.Intent`.
Sources: [packages/next/src/client/components/links.ts:121-141](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L121-L141), [packages/next/src/client/components/links.ts:168-194](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L168-L194), [packages/next/src/client/components/links.ts:249-300](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L249-L300)

> [!NOTE]
> Prefetching on viewport intersection is explicitly disabled in development environments (`NODE_ENV !== 'production'`) for performance reasons to avoid compiling target pages prematurely during local inspection.
> Sources: [packages/next/src/client/components/links.ts:260-265](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L260-L265)

When visible links must be refreshed due to changes in `nextUrl`, the root route tree, or cache invalidations, `pingVisibleLinks()` iterates over `prefetchableAndVisible`. If `isPrefetchTaskDirty()` returns true, it cancels the existing task via `cancelPrefetchTask()` and schedules a new one.
Sources: [packages/next/src/client/components/links.ts:354-386](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L354-L386)

### Programmatic Prefetching API

Beyond automatic link observation, the public `prefetch` function serves as the direct entrypoint for imperative prefetching through router methods or custom link wrappers. It validates the target URL via `createPrefetchURL()`, constructs a cache key incorporating any interception route `nextUrl`, and delegates task creation to the scheduler.
Sources: [packages/next/src/client/components/segment-cache/prefetch.ts:27-47](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/prefetch.ts#L27-L47)

| Function / Method | Input Parameters | Return Type | Purpose |
| :--- | :--- | :--- | :--- |
| `mountLinkInstance` | `element, href, router, fetchStrategy, prefetchEnabled, setOptimisticLinkStatus, ownerStack` | `LinkInstance` | Registers a link element, coerces its URL, and initiates viewport visibility observation if enabled. |
| `mountFormInstance` | `element, href, router, fetchStrategy` | `void` | Registers a form element for prefetch observation based on its action URL. |
| `onLinkVisibilityChanged` | `element, isVisible` | `void` | Updates link visibility state, manages visible tracking sets, and triggers default priority rescheduling. |
| `onNavigationIntent` | `element, unstable_upgradeToDynamicPrefetch` | `void` | Handles hover or touch interactions, optionally upgrading fetch strategy and rescheduling with intent priority. |
| `pingVisibleLinks` | `nextUrl, tree` | `void` | Iterates over visible links, checks for dirty cache states, cancels stale tasks, and reschedules work. |
| `prefetch` | `href, nextUrl, treeAtTimeOfPrefetch, fetchStrategy, onInvalidate` | `void` | Validates an imperative prefetch URL and schedules a prefetch task with default priority and invalidation callback. |

Sources: [packages/next/src/client/components/links.ts:168-232](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L168-L232), [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), [packages/next/src/client/components/links.ts:354-386](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/links.ts#L354-L386), [packages/next/src/client/components/segment-cache/prefetch.ts:27-47](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/prefetch.ts#L27-L47)

## Segment Cache Storage and Invalidation

### Overview

The segment cache manages the storage, lifecycle states, and retention of prefetched React Server Component (RSC) route trees and individual route segments. Stored entries transition through defined status stages while tracking dynamic staleness and vary-path parameters to prevent data races during parallel prefetches.
Sources: [packages/next/src/client/components/segment-cache/scheduler.ts:710-726](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L710-L726), [packages/next/src/client/components/segment-cache/cache.ts:2747-2863](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L2747-L2863)

### Segment Entry Lifecycle and Runtime Fulfills

When a prefetch or runtime request writes server responses into the cache, entries move from uninitialized states to fulfilled or rejected cache records. The function call chain governing runtime entry fulfillment and storage proceeds through:

`writeSeedDataIntoCache()` → `fulfillEntrySpawnedByRuntimePrefetch()` → `fulfillSegmentCacheEntry()` → `setInCacheMap()` or `upsertSegmentEntry()`
Sources: [packages/next/src/client/components/segment-cache/cache.ts:2686-2863](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L2686-L2863)

During this flow, `writeSeedDataIntoCache` recursively unpacks seed data slots. `fulfillEntrySpawnedByRuntimePrefetch` determines whether to re-key the entry under a more generic vary path using `getFulfilledSegmentVaryPath` or `tree.shellVaryPath`. It checks if an entry is owned by the current task via `entriesOwnedByCurrentTask.get(tree.requestKey)`. If owned, it fulfills the existing entry; otherwise, it creates a detached entry or upserts it into the global `segmentCacheMap`.
Sources: [packages/next/src/client/components/segment-cache/cache.ts:2686-2863](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L2686-L2863)

> [!WARNING]
> Never write directly over an entry created by a different task without checking task ownership; doing so introduces data races across concurrent prefetch streams.
> Sources: [packages/next/src/client/components/segment-cache/cache.ts:2796-2802](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L2796-L2802)

### Dynamic Staleness and Stale-Time Resolution

Entries maintain expiration timestamps (`staleAt`) calculated from server-sent headers or async iterables. `getStaleAt` evaluates an optional `staleTimeIterable` by iterating through yielded values and taking the final timestamp, falling back to `getStaleAtFromHeader` or a default static staleness window (`STATIC_STALETIME_MS`). For route tree misses where requests take longer than a minute, a temporary `staleAt` of `now + 60 * 1000` is assigned so that subsequent requests retry instead of blocking indefinitely.
Sources: [packages/next/src/client/components/segment-cache/scheduler.ts:702-709](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L702-L709), [packages/next/src/client/components/segment-cache/cache.ts:3070-3109](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L3070-L3109)

| Entry Status / Parameter | Source Value / Constant | Meaning / Handling |
| :--- | :--- | :--- |
| `EntryStatus.Empty` | Uninitialized / Cache miss | No request in progress; spawns a fetch task and upgrades state. |
| `EntryStatus.Pending` | In-progress request | A fetch is underway; subsequent tasks attach to `blockedTasks`. |
| `EntryStatus.Fulfilled` | Complete cache record | Contains valid RSC data, `staleAt` timestamp, and `isPartial` flag. |
| `EntryStatus.Rejected` | Failed load / 404 | Request failed or was rejected inside a loading boundary. |
| `STATIC_STALETIME_MS` | Fallback duration | Default staleness window applied when no server header or iterable is present. |

Sources: [packages/next/src/client/components/segment-cache/scheduler.ts:684-731](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/scheduler.ts#L684-L731), [packages/next/src/client/components/segment-cache/cache.ts:3060-3109](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/cache.ts#L3060-L3109)

## Optimistic Routing and Route Matching

### Overview

Optimistic Routing enables the client to predict route structures for URLs that have not yet been prefetched by leveraging previously learned route patterns. Stored in a trie indexed by URL path segments (`KnownRoutePart`), these patterns map URL structures to route templates. When a user navigates to a URL with no direct prefetch cache entry, the client matches the candidate URL against the known route tree to synthesize a route entry instantly, avoiding a prefetch round-trip.
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:1-44](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L1-L44)

### Route Pattern Discovery and Trie Population

When the server returns a route tree during an initial load, navigation, or prefetch, the client calls `discoverKnownRoute()`. This function parses the pathname into segments and invokes `discoverKnownRoutePart()`, which walks the route tree and URL parts in parallel to populate the trie.
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:199-272](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L199-L272), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:345-362](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L345-L362)

The call-chain execution walkthrough for discovering and caching a known route proceeds through:

`discoverKnownRoute()` → `fulfillRouteCacheEntry()` → `discoverKnownRoutePart()` → `writeRouteIntoCache()` or `readPattern()`
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:199-272](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L199-L272)

During this recursion, `discoverKnownRoutePart` evaluates whether a segment is static or dynamic, records static siblings into `staticChildren`, and caches the resulting route template in `knownRoutePart.pattern`.
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:370-599](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L370-L599)

> [!WARNING]
> If a static segment or dynamic boundary in the URL does not match the route tree structure, discovery immediately aborts trie population via `handleMismatchDueToRewrite()`, preventing malformed pattern predictions while still writing the valid entry into the standard cache.
> Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:275-306](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L275-L306), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:376-388](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L376-L388)

### Client Route Tree Matching and Reification

When looking up an uncached route, `matchKnownRoute()` splits the pathname and invokes `matchKnownRoutePart()`. Matching prioritizes static child nodes before evaluating dynamic children (`[param]`, `[...param]`, `...param`), collecting parameter values in a `ResolvedParams` map.
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:607-621](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L607-L621), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:716-777](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L716-L777)

Once a matching pattern is found, `reifyRouteTree()` clones the template route tree, substituting the resolved parameter values into dynamic segments and recomputing vary paths to generate a concrete synthetic entry (`FulfilledRouteCacheEntry`).
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:648-693](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L648-L693), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:879-982](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L879-L982)

| Dynamic Parameter Type | Source Identifier | Runtime Matching Behavior |
| :--- | :--- | :--- |
| Regular Dynamic | `'d'` | Consumes exactly 1 URL part and recurses deeper to find leaf patterns. |
| Required Catch-All | `'c'` | Consumes 1 or more remaining URL parts (`pathnameParts.slice(partIndex)`). |
| Optional Catch-All | `'oc'` | Consumes 0 or more URL parts; defaults to an empty array when `urlPart` is null. |
| Intercepted Routes | `'ci(...)'`, `'di(...)'` | Bails out to server resolution because behavior depends on navigation referrer context. |

Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:780-845](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L780-L845)

> [!NOTE]
> The trie distinguishes between a `null` and an empty `Map` for `staticChildren`: `null` indicates that static siblings are completely unknown (such as in webpack development mode on-demand compilation), forcing the matcher to deopt to server resolution rather than risk false-positive dynamic matches.
> Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:98-106](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L98-L106), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:732-741](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L732-L741)

### Rewrite Fallback Handling

Optimistic routing incorporates protection against dynamic rewrites and path mismatches. If the server returns a response whose pathname diverges from what was predicted, the route entry is marked with `hasDynamicRewrite = true`.
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:227-230](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L227-L230), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:564-567](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L564-L567)

When `matchKnownRoute` encounters a pattern where `hasDynamicRewrite` is true, or where `couldBeIntercepted` is set, it rejects the prediction and returns `null`, forcing the router to fall back to standard server resolution.
Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:641-643](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L641-L643), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:735-738](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L735-L738)

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Trie-based `KnownRoutePart` storage | Fast O(path length) client-side lookups | Append-only structure with no eviction inside sessions |
| Pattern reification via cloning | Instantly produces valid synthetic cache entries | Allocates new tree structures on cache prediction hits |
| Static children priority over dynamic | Prevents static routes from capturing dynamic matches | Requires tracking static siblings during discovery |

Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:33-44](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L33-L44), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:745-748](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L745-L748), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:879-885](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L879-L885)

## PPR Navigation Execution and Reconciliation

### Overview

Partial Prerendering (PPR) navigation execution bridges client-side cache traversal and server-driven dynamic updates. When a user navigates to a new location via `navigate()`, the router checks the route segment cache for a fulfilled entry. If a matching route tree is found, it immediately builds a copy-on-write `NavigationTask` and patches the app router state, allowing the static prefetch shell to render instantly while any missing dynamic data is deferred.
Sources: [packages/next/src/client/components/segment-cache/navigation.ts:58-148](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L58-L148), [packages/next/src/client/components/router-reducer/ppr-navigations.ts:163-190](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L163-L190)

### Navigation Execution Walkthrough

The navigation and reconciliation pipeline flows through a series of deterministic functions that transition raw URL requests into updated router trees and dynamic server fetches:

`navigate()` → `navigateImpl()` → `navigateUsingPrefetchedRouteTree()` → `navigateToKnownRoute()` → `startPPRNavigation()` → `updateCacheNodeOnNavigation()`
Sources: [packages/next/src/client/components/segment-cache/navigation.ts:58-387](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L58-L387), [packages/next/src/client/components/router-reducer/ppr-navigations.ts:190-228](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L190-L228)

1. `navigate()` acts as the entry point, coordinating testing locks and calling `navigateImpl()`.
Sources: [packages/next/src/client/components/segment-cache/navigation.ts:58-112](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L58-L112)
2. `navigateImpl()` queries the route cache using `readRouteCacheEntry()`. If fulfilled, it invokes `navigateUsingPrefetchedRouteTree()`.
Sources: [packages/next/src/client/components/segment-cache/navigation.ts:114-149](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L114-L149)
3. `navigateUsingPrefetchedRouteTree()` extracts the target `RouteTree` and delegates to `navigateToKnownRoute()`.
Sources: [packages/next/src/client/components/segment-cache/navigation.ts:360-387](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L360-L387)
4. `navigateToKnownRoute()` sets up a `NavigationRequestAccumulation` context and invokes `startPPRNavigation()`.
Sources: [packages/next/src/client/components/segment-cache/navigation.ts:214-328](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L214-L328)
5. `startPPRNavigation()` wraps the root refresh state and calls `updateCacheNodeOnNavigation()`.
Sources: [packages/next/src/client/components/router-reducer/ppr-navigations.ts:190-228](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L190-L228)
6. `updateCacheNodeOnNavigation()` compares the new route segments against the old `FlightRouterState`, determining whether to reuse cached nodes or switch to `createCacheNodeOnNavigation()` for divergent subtrees.
Sources: [packages/next/src/client/components/router-reducer/ppr-navigations.ts:230-256](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L230-L256)

> [!NOTE]
> If `startPPRNavigation()` returns `null`, indicating that no SPA-compatible transitions could be resolved, the router falls back to `completeHardNavigation()`, executing a traditional full-page MPA reload.
> Sources: [packages/next/src/client/components/segment-cache/navigation.ts:356-358](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L356-L358)

### Freshness Policies and Task Status

Navigation tasks and freshness rules dictate how cache entries and server requests interact during routing operations.

| Enum / Type Name | Member / Value | Purpose & Runtime Behavior |
| :--- | :--- | :--- |
| `FreshnessPolicy` | `Default` | Standard user-initiated navigation through links or router pushes. |
| `FreshnessPolicy` | `Hydration` | Initial application load from server-embedded HTML seed data. |
| `FreshnessPolicy` | `HistoryTraversal` | Browser back/forward history navigation restoring cached states. |
| `FreshnessPolicy` | `RefreshAll` | Full router refresh explicitly requested by user actions or triggers. |
| `FreshnessPolicy` | `HMRRefresh` | Development-mode Fast Refresh updating modified modules. |
| `FreshnessPolicy` | `Gesture` | Pointer gesture or hover-triggered prefetch hint navigation. |
| `NavigationTaskStatus` | `Pending` | Task requires a dynamic server request to resolve missing holes. |
| `NavigationTaskStatus` | `Fulfilled` | Task is fully static or populated with available cache seed data. |
| `NavigationTaskStatus` | `Rejected` | Task failed or encountered a route tree mismatch. |
| `NavigationTaskExitStatus` | `Done` | No additional navigation actions or retries are required. |
| `NavigationTaskExitStatus` | `SoftRetry` | Data failed to load due to tree mismatch; retry soft navigation. |
| `NavigationTaskExitStatus` | `HardRetry` | Unrecoverable failure in parallel route; fall back to MPA retry. |

Sources: [packages/next/src/client/components/router-reducer/ppr-navigations.ts:66-118](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/router-reducer/ppr-navigations.ts#L66-L118)

> [!WARNING]
> During gesture navigations (`FreshnessPolicy.Gesture`), dynamic request spawning is deliberately suppressed by `navigateToKnownRoute()` to avoid wasteful server invocations on mere hover events before an actual click occurs.
> Sources: [packages/next/src/client/components/segment-cache/navigation.ts:330-341](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation.ts#L330-L341)

## Server Prerendering and Staged Payloads

### Overview

During static generation with Partial Prerendering (PPR) enabled (`experimental.isRoutePPREnabled`), Next.js manages server prerendering through a staged process that isolates dynamic holes, tracks dynamic access patterns, and produces static Flight streams. 
Sources: [packages/next/src/server/app-render/app-render.tsx:8052-8056](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8052-L8056)

### Call-Chain Execution Walkthrough

The server prerendering sequence coordinates dynamic tracking stores, RSC payload generation, React Server streaming, and HTML prelude processing:
1. `createDynamicTrackingState()` initializes dynamic tracking stores based on debug options.
Sources: [packages/next/src/server/app-render/app-render.tsx:8054-8054](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8054-L8054)
2. `workUnitAsyncStorage.run()` binds the `pprReactServerPrerenderStore` context to execute `getRSCPayload()`.
Sources: [packages/next/src/server/app-render/app-render.tsx:8070-8076](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8070-L8076)
3. `createReactServerPrerenderResultFromRender()` wraps the result of `renderFlightStream()`, producing the unclosing server stream.
Sources: [packages/next/src/server/app-render/app-render.tsx:8079-8091](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8079-L8091)
4. `getClientPrerender()` renders the `<App />` tree using the PPR prerender store, returning an `unprocessedPrelude` and `postponed` state.
Sources: [packages/next/src/server/app-render/app-render.tsx:8107-8127](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8107-L8127)
5. `streamToBuffer()` reads the full React Server render stream into `flightData`, which is then passed to `collectSegmentData()` if `shouldGenerateStaticFlightData()` evaluates to true.
Sources: [packages/next/src/server/app-render/app-render.tsx:8139-8151](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8139-L8151)
6. `processPreludeOp()` processes the unprocessed prelude to yield the final `prelude` and `preludeIsEmpty` status.
Sources: [packages/next/src/server/app-render/app-render.tsx:8153-8154](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8153-L8154)

> [!NOTE]
> Awaiting the complete RSC render stream via `streamToBuffer(reactServerResult.asStream())` guarantees that dynamic API usages anywhere within the Server Component tree are captured—even if those specific branches are omitted from the initial SSR HTML prelude.
> Sources: [packages/next/src/server/app-render/app-render.tsx:8136-8140](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8136-L8140)

### Prerender Outcomes and Postponed State Generation

When prerendering completes, Next.js inspects the dynamic access tracking to categorize the output into one of three distinct outcomes: Dynamic HTML, Dynamic Data, or fully Static.
Sources: [packages/next/src/server/app-render/app-render.tsx:8156-8171](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8156-L8171)

| Prerender Outcome | Detection Condition | Handling & Postponed State |
| :--- | :--- | :--- |
| `Dynamic HTML` | `accessedDynamicData()` is true AND `postponed != null` | Generates postponed state via `getDynamicHTMLPostponedState()` using `DynamicHTMLPreludeState.Empty` or `Full`. |
| `Dynamic Data` | `accessedDynamicData()` is true AND `postponed == null` | Generates postponed state via `getDynamicDataPostponedState()` without resuming HTML shells. |
| `Static` | `accessedDynamicData()` is false | Statically encodes all server-inserted HTML and Flight data without dynamic holes. |

Sources: [packages/next/src/server/app-render/app-render.tsx:8171-8189](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8171-L8189)

> [!WARNING]
> If a prerender has dynamic holes (`Dynamic HTML`), the engine skips embedding server-inserted HTML and inlined Flight data into the static output, requiring runtime resumption when client requests arrive.
> Sources: [packages/next/src/server/app-render/app-render.tsx:8159-8163](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L8159-L8163)

## Back Forward Cache Integration

### Overview

The back-forward cache (`bfcache`) integrates with client-side routing to persist session history state, coordinate dynamic segment upgrades, and manage the cache restore lifecycle across history traversals and regular navigations. It maintains a separate memory store (`bfcacheMap`) using the `CacheMap` data structure, tracking `BFCacheEntry` records containing rendered server components (`rsc`), prefetched server components (`prefetchRsc`), page metadata (`head`), prefetched metadata (`prefetchHead`), persistent `bfcacheId` values, and staleness parameters.
Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:32-60](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L32-L60)

### Cache Restore Lifecycle and Operations

The back-forward cache exposes several exported functions to write, read, and invalidate persisted entry states depending on navigation type and staleness conditions:
- `invalidateBfCache()` increments `currentBfCacheVersion`, invalidating existing back-forward cache entries when called in a browser environment.
Sources: [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)
- `writeToBFCache()` constructs a `BFCacheEntry` with `status: EntryStatus.Fulfilled` and stores it under the provided `varyPath`.
Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:70-114](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L70-L114)
- `writeHeadToBFCache()` delegates head-data writing directly to `writeToBFCache()`.
Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:116-135](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L116-L135)
- `updateBFCacheEntryStaleAt()` retrieves an entry bypassing staleness checks using `-1` and updates its `staleAt` property with a per-page value from `unstable_dynamicStaleTime`.
Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:137-163](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L137-L163)
- `readFromBFCache()` queries `bfcacheMap` passing `-1` as the timestamp to bypass staleness evaluation during back-forward history traversals.
Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:165-183](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L165-L183)
- `readFromBFCacheDuringRegularNavigation()` evaluates entries against the real `now` timestamp during standard navigations.
Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:185-201](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L185-L201)

> [!NOTE]
> During a back-forward navigation, `readFromBFCache` passes `-1` instead of the current timestamp to `getFromCacheMap`, explicitly bypassing staleness checks so cached session history state is always restored regardless of age.
> Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:171-183](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L171-L183)

### Dynamic Segment Upgrades and Staleness Computation

Dynamic stale times received via the Flight response `d` field are normalized into absolute timestamps via `computeDynamicStaleAt()`, falling back to `DYNAMIC_STALETIME_MS` when `UnknownDigitalStaleTime` (`-1`) is supplied.
Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:5-22](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L5-L22)

| Function Name | Parameters | Return Type | Purpose |
| :--- | :--- | :--- | :--- |
| `computeDynamicStaleAt` | `now: number`, `dynamicStaleTimeSeconds: number` | `number` | Converts server-sent dynamic stale seconds into an absolute millisecond timestamp. |
| `invalidateBfCache` | *none* | `void` | Increments `currentBfCacheVersion` to invalidate stale session records. |
| `writeToBFCache` | `now`, `varyPath`, `rsc`, `prefetchRsc`, `head`, `prefetchHead`, `dynamicStaleAt`, `bfcacheId` | `void` | Stores a completed navigation entry in `bfcacheMap`. |
| `readFromBFCache` | `varyPath: SegmentVaryPath` | `BFCacheEntry \| null` | Retrieves a cached history entry bypassing timestamp checks. |
| `readFromBFCacheDuringRegularNavigation` | `now: number`, `varyPath: SegmentVaryPath` | `BFCacheEntry \| null` | Queries cached entries subject to normal TTL and staleness validation. |

Sources: [packages/next/src/client/components/segment-cache/bfcache.ts:15-201](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/bfcache.ts#L15-L201)

## Navigation Locks and Debugging Channels

### Instant Navigation Testing Synchronization Locks

The Instant Navigation Testing API manages synchronization via an in-memory lock (`NavigationLockState`) and a persistent cookie (`NEXT_INSTANT_TEST_COOKIE`). When an external testing harness or devtools initiates a capture scope, it creates a pending cookie state. Next.js reads this state, acquires the lock, and intercepts outgoing client fetches.

| Cookie State | Raw Value Condition | Meaning |
| :--- | :--- | :--- |
| `empty` | `raw === ''` | No testing lock cookie is present. |
| `pending` | `parsed[2]` is neither `null` nor present as object | External actor initiated a pending navigation test scope. |
| `mpa` | `parsed[2] === null` | Static shell served; captured Multi-Page Application mode. |
| `spa` | `parsed[2]` is an object (`from` / `to` tree) | Prefetch resolved; captured Single-Page Application mode. |

Sources: [packages/next/src/client/components/segment-cache/navigation-testing-lock.ts:21-37](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation-testing-lock.ts#L21-L37)

The lock lifecycle transitions through exact internal functions: `startListeningForInstantNavigationCookie()` inspects initial state and attaches listeners, calling `acquireLock()` to instantiate a promise and override `window.fetch` with `globalFetchOverride`, and invoking `releaseLock()` alongside `refreshOnInstantNavigationUnlock()` when the test cookie is deleted.
Sources: [packages/next/src/client/components/segment-cache/navigation-testing-lock.ts:87-118](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation-testing-lock.ts#L87-L118), [packages/next/src/client/components/segment-cache/navigation-testing-lock.ts:158-216](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation-testing-lock.ts#L158-L216)

> [!WARNING]
> `globalFetchOverride` pins execution to the pre-lock `window.fetch` captured during `acquireLock()`. If a user-installed fetch override is attached after the lock scope begins, it remains bypassed until the navigation lock is fully released and the original fetch reference is restored.
> Sources: [packages/next/src/client/components/segment-cache/navigation-testing-lock.ts:131-150](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/navigation-testing-lock.ts#L131-L150)

### Development Debug Communication Streams

The development debug channel streams diagnostic chunks for requests identified by `NEXT_REQUEST_ID_HEADER` or `self.__next_r`. Initial document debug streams are buffered using a `TransformStream`, written asynchronously to IndexedDB (`__next_debug_channel` database under the `channels` store) during idle periods (`whenIdle()`), and pruned to maintain a maximum bound of `10` entries using the `createdAt` index.

| Constant Name | Value | Purpose |
| :--- | :--- | :--- |
| `DB_NAME` | `'__next_debug_channel'` | IndexedDB database identifier for persisted debug chunks. |
| `STORE_NAME` | `'channels'` | Object store holding `DebugChannelEntry` records keyed by `requestId`. |
| `CREATED_AT_INDEX` | `'createdAt'` | Index name used for ordered cursor traversal and pruning. |
| `MAX_ENTRIES` | `10` | Maximum number of debug entries retained before oldest pruning occurs. |

Sources: [packages/next/src/client/dev/debug-channel.ts:11-20](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/debug-channel.ts#L11-L20)

When a cached HTML document is restored from the browser cache, `createDebugChannel()` evaluates navigation timing metrics via `wasServedFromCacheKnownAtExec()` and `wasServedFromCacheAtPageshow()`. If chunks are missing from IndexedDB during a cache restore, `restoreDebugChannelOrReload()` triggers an unconditional `location.reload()` while parking the stream to prevent hydration errors.
Sources: [packages/next/src/client/dev/debug-channel.ts:200-285](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/debug-channel.ts#L200-L285), [packages/next/src/client/dev/debug-channel.ts:361-432](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/debug-channel.ts#L361-L432)

> [!NOTE]
> `wasServedFromCacheKnownAtExec()` checks Safari's tab-duplication signature (`type === 'navigate'`, `responseStart === 0`, `responseEnd > 0`) alongside standard `transferSize` and `encodedBodySize` metrics to distinguish HTTP cache restorations from fresh server fetches prior to the `pageshow` event.
> Sources: [packages/next/src/client/dev/debug-channel.ts:200-251](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/debug-channel.ts#L200-L251)

## Related

- [Client Segment Cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-routing/client-segment-cache)
- [Staged Dynamic Rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-router-rendering/staged-dynamic-rendering)


## Sitemap

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