---
title: "Staged Dynamic Rendering"
description: "Staged Dynamic Rendering is an advanced rendering architecture in Next.js that structures server-side rendering into discrete lifecycle stages—ranging from early static shell generation through run..."
last_updated: "2026-09-23T10:52:03.167933+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-router-rendering/staged-dynamic-rendering"
---

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

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

- [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/app-render/dynamic-rendering.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.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/base-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/base-server.ts)
- [packages/next/src/server/route-modules/app-route/module.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-route/module.ts)
- [packages/next/src/server/app-render/staged-rendering.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts)
- [packages/next/src/server/request/io.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/io.ts)
- [packages/next/src/server/app-render/postponed-state.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts)
- [packages/next/src/server/route-modules/pages/pages-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/pages/pages-handler.ts)
- [packages/next/src/server/node-environment-extensions/io-utils.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx)
- [packages/next/src/server/dynamic-rendering-utils.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dynamic-rendering-utils.ts)
- [packages/next/src/server/app-render/sync-io-messages.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/sync-io-messages.ts)
- [packages/next/src/server/app-render/vary-params.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts)
- [packages/next/src/client/components/navigation.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts)
- [packages/next/src/server/request/params.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts)
</details>

## Overview

Staged Dynamic Rendering is an advanced rendering architecture in Next.js that structures server-side rendering into discrete lifecycle stages—ranging from early static shell generation through runtime phases to fully dynamic rendering—controlled by the `StagedRenderingController`. This model allows Next.js to isolate static components and render cacheable data without deopting an entire React tree, replacing coarse deopts with fine-grained stage progression. By coordinating triggers, tracking dynamic data access, and orchestrating delayed parameter resolutions, the system prevents unnecessary blocking while managing cache interactions and serializing Flight streams effectively.

Sources: [packages/next/src/server/app-render/app-render.tsx:944-952](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L944-L952), [packages/next/src/server/app-render/staged-rendering.ts:4-20](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L4-L20), [packages/next/src/server/app-render/dynamic-rendering.ts:6-10](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L6-L10)

## Staged Rendering Architecture and Controller

### Overview

The staged rendering lifecycle divides server rendering into sequential, controllable execution steps managed by the `StagedRenderingController` class in `staged-rendering.ts`. Progression through these stages controls when specific data kinds—such as session data, static link data, and runtime link data—become available to the React rendering tree. The controller maintains an internal map of triggers for every advanceable render stage, resolving pending promises as the render moves forward.

Sources: [packages/next/src/server/app-render/staged-rendering.ts:4-20](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L4-L20), [packages/next/src/server/app-render/staged-rendering.ts:84-106](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L84-L106)

### Render Stages and Progression Order

The progression is governed by `RENDER_STAGE_ADVANCE_ORDER`, which sequences static and runtime phases into distinct shell, early, and standard milestones before reaching the final dynamic stage.

| Render Stage | Enum Value | Category / Meaning |
| :--- | :--- | :--- |
| `RenderStage.Before` | `1` | Initial state prior to render execution. |
| `RenderStage.ShellEarlyStatic` | `10` | Early static shell phase. |
| `RenderStage.ShellStatic` | `11` | Late static shell phase (`FIRST_LATE_RENDER_STAGE`). |
| `RenderStage.EarlyStatic` | `12` | Early static data phase. |
| `RenderStage.Static` | `13` | Standard static rendering phase. |
| `RenderStage.ShellEarlyRuntime` | `20` | Early runtime session shell phase. |
| `RenderStage.ShellRuntime` | `21` | Late runtime session shell phase. |
| `RenderStage.EarlyRuntime` | `22` | Early runtime data phase. |
| `RenderStage.Runtime` | `23` | Standard runtime rendering phase. |
| `RenderStage.Dynamic` | `30` | Fully dynamic fallback rendering stage. |
| `RenderStage.Abandoned` | `40` | Render aborted or abandoned state. |

Sources: [packages/next/src/server/app-render/staged-rendering.ts:4-41](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L4-L41)

### Controller Trigger Coordination and Lifecycle Execution

The `StagedRenderingController` constructor initializes stage triggers and wires up event listeners for abort signals and abandon controllers. When `advanceStage(targetStage)` is called, the controller verifies that the target does not exceed `finalStage` and checks whether the target is ahead of `currentStage`. 

Sources: [packages/next/src/server/app-render/staged-rendering.ts:108-148](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L108-L148), [packages/next/src/server/app-render/staged-rendering.ts:314-325](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L314-L325)

The stage advancement flow executes through the following call chain:
`StagedRenderingController.advanceStage()` → determines index range via `RENDER_STAGE_ADVANCE_ORDER.indexOf()` → iterates over intermediate stages calling `this.resolveStage()` → `fireStageTrigger()` → executes registered listeners in `trigger._listeners` and invokes `trigger._resolvePromise()`.

```typescript
// Example instantiation and stage progression sequence in node rendering
const stageController = new StagedRenderingController({
  abortSignal: null,
  abandonController: null,
  shouldTrackSyncIO: false,
  finalStage: null,
})

// Advance through static stages during node flight stream generation
stageController.advanceStage(RenderStage.ShellStatic)
stageController.advanceStage(RenderStage.Static)
stageController.advanceStage(RenderStage.Dynamic)
```

Sources: [packages/next/src/server/app-render/app-render.tsx:944-952](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L944-L952), [packages/next/src/server/app-render/app-render.tsx:1035-1073](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1035-L1073), [packages/next/src/server/app-render/staged-rendering.ts:314-362](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L314-L362), [packages/next/src/server/app-render/staged-rendering.ts:440-466](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L440-L466)

> [!NOTE]
> `cancelStageTrigger` suppresses unhandled rejection warnings automatically by attaching a no-op catch handler to `trigger.promise` when an abort signal rejects pending stage triggers.

Sources: [packages/next/src/server/app-render/staged-rendering.ts:124-137](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L124-L137), [packages/next/src/server/app-render/staged-rendering.ts:469-482](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L469-L482)

## Dynamic Data Access and Postpone Handling

### Overview

Dynamic data access and postponement handling govern how Next.js tracks runtime variables, deopts component trees, and coordinates client-side navigation hooks. When code reads dynamic properties or navigation parameters during prerendering, Next.js captures these occurrences via explicit tracking structures, triggers React postponements, or aborts static generation depending on the active render unit and configuration flags.

Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:1-21](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L1-L21), [packages/next/src/server/app-render/dynamic-rendering.ts:167-236](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L167-L236)

### Dynamic Tracking State and Access Annotation

The `DynamicTrackingState` structure manages properties recorded during Server Component rendering. It maintains `isDebugDynamicAccesses`, an array of `dynamicAccesses` storing individual `DynamicAccess` objects containing an optional stack trace and the accessed expression string, alongside sync error handling flags `syncDynamicErrorWithStack` and `syncDynamicErrorWithStackPostMicrotask`. 

Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:84-112](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L84-L112)

The `createDynamicTrackingState(isDebugDynamicAccesses)` function instantiates this tracking object with an empty `dynamicAccesses` array and null error states. When a dynamic scope is entered, `annotateDynamicAccess(expression, prerenderStore)` pushes a new entry into `dynamicAccesses`, capturing `new Error().stack` if `isDebugDynamicAccesses` is enabled.

Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:124-133](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L124-L133), [packages/next/src/server/app-render/dynamic-rendering.ts:612-625](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L612-L625)

| Structure / Function | Type / Signature | Purpose |
| :--- | :--- | :--- |
| `DynamicAccess` | `object` | Holds optional stack trace and string expression for a dynamic read. |
| `DynamicTrackingState` | `object` | Container tracking `isDebugDynamicAccesses`, `dynamicAccesses`, and sync error states. |
| `createDynamicTrackingState` | `(isDebugDynamicAccesses?: boolean) => DynamicTrackingState` | Factory initializing a clean dynamic tracking state record. |
| `annotateDynamicAccess` | `(expression: string, prerenderStore: PrerenderStoreModern | ValidationStoreClient) => void` | Appends a dynamic access record to the tracking state if present. |

Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:84-133](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L84-L133), [packages/next/src/server/app-render/dynamic-rendering.ts:612-625](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L612-L625)

### Postponed State Mechanics and Parsing

Postponed state representations handle data and HTML segments that suspend during partial prerendering (PPR). The `DynamicState` enum distinguishes between RSC render data (`DATA = 1`) and HTML shell render phases (`HTML = 2`).

Sources: [packages/next/src/server/app-render/postponed-state.ts:15-25](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts#L15-L25)

| Enum / Value | Value / Properties | Meaning |
| :--- | :--- | :--- |
| `DynamicState.DATA` | `1` | Dynamic access occurred during the React Server Component render phase. |
| `DynamicState.HTML` | `2` | Dynamic access occurred during the HTML shell render phase. |
| `DynamicDataPostponedState` | `{ type: DynamicState.DATA, renderResumeDataCache }` | Postponed state for dynamic data payload. |
| `DynamicHTMLPostponedState` | `{ type: DynamicState.HTML, data: [...], renderResumeDataCache }` | Postponed state containing prelude state, React postponed object, and cache. |

Sources: [packages/next/src/server/app-render/postponed-state.ts:15-63](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts#L15-L63)

The `parsePostponedState(state, interpolatedParams, maxPostponedStateSizeBytes)` function parses a serialized postponed state string by extracting the initial length match, slicing the postponed string payload and resume data cache, and replacing fallback route parameters using `getDynamicParam` when interpolated params are provided.

Sources: [packages/next/src/server/app-render/postponed-state.ts:117-216](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts#L117-L216)

> [!WARNING]
> If parsing fails due to malformed string prefixes or JSON errors, `parsePostponedState` catches the exception, logs it, and falls back to a default `DynamicDataPostponedState` instance rather than crashing the request parser.

Sources: [packages/next/src/server/app-render/postponed-state.ts:203-215](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts#L203-L215)

### Client Navigation Hook Dynamic Bailouts

Client hooks such as `usePathname`, `useSearchParams`, `useParams`, `useSelectedLayoutSegments`, and `useSelectedLayoutSegment` invoke `useDynamicRouteParams` or `useDynamicSearchParams` during server-side rendering to signal runtime dependency.

Sources: [packages/next/src/client/components/navigation.ts:65-66](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts#L65-L66), [packages/next/src/client/components/navigation.ts:122-123](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts#L122-L123), [packages/next/src/client/components/navigation.ts:225-226](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts#L225-L226), [packages/next/src/client/components/navigation.ts:277-280](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts#L277-L280), [packages/next/src/client/components/navigation.ts:332-335](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts#L332-L335)

The dynamic hook invocation trace flows through the following call chain:
1. `useSelectedLayoutSegment()` calls `useSelectedLayoutSegments(parallelRouteKey)` — accesses layout context and retrieves parallel segment paths.
2. `useSelectedLayoutSegments()` invokes `useDynamicRouteParams('useSelectedLayoutSegments()')` — checks store types and manages cache components fallback parameters.
3. `useDynamicRouteParams()` evaluates work unit stores (`prerender-client`) and uses `makeClientHookHangingPromise` to suspend rendering when fallback parameters are present.

Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:627-642](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L627-L642), [packages/next/src/client/components/navigation.ts:332-337](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts#L332-L337)

```mermaid
sequenceDiagram
    participant Nav as next/navigation
    participant Dyn as dynamic-rendering.ts
    participant Work as work-unit-async-storage
    Nav->>Dyn: useSelectedLayoutSegment()
    Dyn->>Nav: useSelectedLayoutSegments()
    Nav->>Dyn: useDynamicRouteParams('useSelectedLayoutSegment()')
    Dyn->>Work: workUnitAsyncStorage.getStore()
    Work-->>Dyn: prerender-client workUnitStore
    Dyn->>Dyn: makeClientHookHangingPromise()
```

Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:627-642](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L627-L642), [packages/next/src/client/components/navigation.ts:332-337](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/navigation.ts#L332-L337)

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| Separation of `DynamicState.DATA` and `HTML` | Precise tracking of whether access occurred during RSC or HTML shell generation | Additional branch handling in postponed state parsers |
| Hanging promises for client hooks in `prerender-client` | Allows components to suspend cleanly as dynamic holes during PPR | Requires robust abort signal listeners to reject pending hanging promises on timeout |
| Fallback route param replacement strings | Enables serialization of postponed states with dynamic parameter segments | String manipulation overhead during state re-hydration |

Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:627-642](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L627-L642), [packages/next/src/server/app-render/postponed-state.ts:15-25](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts#L15-L25), [packages/next/src/server/app-render/postponed-state.ts:168-190](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts#L168-L190), [packages/next/src/server/dynamic-rendering-utils.ts:108-113](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dynamic-rendering-utils.ts#L108-L113)

## Dynamic Params and Staged Resolution

### Overview

Parameter resolution within staged rendering bridges static shells and dynamic server execution. Route parameters (`params`) and search parameters (`searchParams`) are accessed via async promises that leverage staged progression controls to delay unblocking until specific render boundaries or segment stages are reached. 

Sources: [packages/next/src/server/request/params.ts:331-380](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts#L331-L380), [packages/next/src/server/app-render/vary-params.ts:21-35](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts#L21-L35)

### Parameter Promise Lifecycle and Staged Resolution

The lifecycle of parameter resolution coordinates execution across static and dynamic boundaries. When server routes evaluate parameters during prerendering, `createServerParamsForRoute` determines the appropriate execution path based on the active `WorkUnitStore` type.

Sources: [packages/next/src/server/request/params.ts:138-204](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts#L138-L204)

The call-chain execution walkthrough for parameter resolution proceeds as follows:
1. `createServerParamsForRoute` retrieves the current `workUnitStore` and dispatches static route params to `createStaticPrerenderParams`.
Sources: [packages/next/src/server/request/params.ts:138-158](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts#L138-L158)
2. `createStaticPrerenderParams` inspects whether `__NEXT_APP_SHELLS` is active and invokes `stagedRendering.delayUntilStage` with the late static link data stage (`RenderStage.Static`).
Sources: [packages/next/src/server/request/params.ts:359-380](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts#L359-L380), [packages/next/src/server/dynamic-rendering-utils.ts:198-199](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dynamic-rendering-utils.ts#L198-L199)
3. `delayUntilStage` obtains the underlying stage promise by invoking `this.getStagePromise(stage)`.
Sources: [packages/next/src/server/app-render/staged-rendering.ts:372-377](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L372-L377)
4. `getStagePromise` returns `this.triggers[stage].promise`, which remains pending until the staged rendering controller advances past that specific milestone.
Sources: [packages/next/src/server/app-render/staged-rendering.ts:364-366](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L364-L366)

```mermaid
sequenceDiagram
    participant Route as params.ts
    participant Static as createStaticPrerenderParams
    participant Controller as StagedRenderingController
    participant Trigger as StageTrigger
    Route->>Static: createServerParamsForRoute()
    Static->>Controller: delayUntilStage(RenderStage.Static)
    Controller->>Trigger: getStagePromise(RenderStage.Static)
    Trigger-->>Controller: pending promise
    Controller-->>Route: delayed promise
```

Sources: [packages/next/src/server/request/params.ts:138-158](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts#L138-L158), [packages/next/src/server/request/params.ts:359-380](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts#L359-L380), [packages/next/src/server/app-render/staged-rendering.ts:364-377](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L364-L377)

> [!NOTE]
> Even when parameters are entirely static, they are intentionally excluded from the initial HTML shell by delaying their resolution until the static stage is reached.

Sources: [packages/next/src/server/request/params.ts:359-380](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/params.ts#L359-L380)

### Vary-Params Tracking and Accumulators

To support granular segment caching and flight serialization, Next.js tracks which parameter keys are accessed during rendering using `VaryParamsAccumulator` structures.

Sources: [packages/next/src/server/app-render/vary-params.ts:21-35](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts#L21-L35)

| Accumulator Field / Function | Type / Return | Purpose |
| :--- | :--- | :--- |
| `varyParams` | `VaryParams` (`Set<string>`) | Mutable set accumulating parameter property accesses during render |
| `status` | `'pending' \| 'fulfilled'` | Tracks whether the thenable has finalized its vary parameter set |
| `value` | `VaryParams` | Finalized parameter key set exposed to React Flight |
| `createResponseVaryParamsAccumulator` | `ResponseVaryParamsAccumulator` | Initializes head, rootParams, and segment tracking sets |
| `createVaryingParams` | `Params` | Wraps route parameters in getter properties or Proxies to record reads |

Sources: [packages/next/src/server/app-render/vary-params.ts:21-105](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts#L21-L105), [packages/next/src/server/app-render/vary-params.ts:244-298](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts#L244-L298)

> [!TIP]
> When optional catch-all parameters are present (`...slug`), `createVaryingParams` employs a JavaScript `Proxy` to intercept `get`, `has`, and `ownKeys` traps, ensuring missing properties and enumerations correctly register as varying accesses.

Sources: [packages/next/src/server/app-render/vary-params.ts:249-281](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts#L249-L281)

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| `Object.defineProperty` getters for standard parameters | High engine optimization potential for property reads | Requires exact key enumeration upfront |
| `Proxy` trap wrapping for optional catch-all parameters | Captures missing keys, `in` checks, and `Object.keys()` iterations | Higher runtime overhead compared to native property getters |
| Singleton `emptyVaryParamsAccumulator` | Zero memory allocation and immediate resolution for static client components | Limited to parameter-free segments |

Sources: [packages/next/src/server/app-render/vary-params.ts:71-91](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts#L71-L91), [packages/next/src/server/app-render/vary-params.ts:244-298](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/vary-params.ts#L244-L298)

## Sync IO Tracking and Node Environment Extensions

### Overview

Next.js intercepts synchronous platform operations (such as reads of current time, random number generators, or cryptographic functions) during pre-rendering to prevent non-deterministic values from being baked into static outputs. When a synchronous I/O action occurs within a pre-render or staged execution context, platform extensions capture the access, format specific diagnostic messages, and trigger stage interruptions.

Sources: [packages/next/src/server/node-environment-extensions/io-utils.tsx:13-103](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L13-L103), [packages/next/src/server/app-render/sync-io-messages.ts:33-85](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/sync-io-messages.ts#L33-L85)

### Synchronous Platform IO Interception and Call-Chain Execution

When code executes inside a Node environment extension, `io()` intercepts calls using a three-argument signature: `io(expression: string, type: SyncIOApiType)`. The execution flows through work-unit storage checks and controller evaluation.

```mermaid
sequenceDiagram
    participant Caller as User Code / Platform API
    participant IOExt as io-utils.tsx (`io`)
    participant WorkUnit as workUnitAsyncStorage
    participant Controller as StagedRenderingController
    Caller->>IOExt: io(expression, type)
    IOExt->>WorkUnit: getStore()
    WorkUnit-->>IOExt: workUnitStore
    alt workUnitStore.type is 'prerender' or 'prerender-runtime'
        IOExt->>IOExt: check prerenderSignal.aborted
        IOExt->>Dynamic: abortOnSynchronousPlatformIOAccess(...)
    else workUnitStore.type is 'request'
        IOExt->>Controller: shouldTrackSyncInterrupt()
        Controller-->>IOExt: true/false
        IOExt->>Controller: syncInterruptCurrentStageWithReason(syncIOError)
    end
```

Sources: [packages/next/src/server/node-environment-extensions/io-utils.tsx:13-103](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L13-L103), [packages/next/src/server/app-render/staged-rendering.ts:154-185](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L154-L185)

The detailed call chain proceeds as follows:
1. `io(expression, type)` retrieves `workUnitAsyncStorage` and `workAsyncStorage`. Sources: [packages/next/src/server/node-environment-extensions/io-utils.tsx:13-15](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L13-L15)
2. Depending on `workUnitStore.type`, if it matches `'prerender'`, `'prerender-runtime'`, or `'prerender-client'`, it checks whether `prerenderSignal.aborted` is `false`. Sources: [packages/next/src/server/node-environment-extensions/io-utils.tsx:21-54](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L21-L54)
3. It calls `abortOnSynchronousPlatformIOAccess(...)`, which records the stack via `applyOwnerStack(createSyncIOError(...))` into `dynamicTracking.syncDynamicErrorWithStack` and invokes `prerenderStore.controller.abort(error)`. Sources: [packages/next/src/server/app-render/dynamic-rendering.ts:323-342](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-rendering.ts#L323-L342), [packages/next/src/server/node-environment-extensions/io-utils.tsx:29-34](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L29-L34)
4. If `workUnitStore.type` is `'request'`, it inspects `stageController.shouldTrackSyncInterrupt()`. Sources: [packages/next/src/server/node-environment-extensions/io-utils.tsx:55-57](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L55-L57)
5. It creates either `createSyncIOError` or `createSyncIORuntimeError` based on whether the current stage is `Static`/`EarlyStatic` or `Runtime`, wraps it with `applyOwnerStack()`, and invokes `stageController.syncInterruptCurrentStageWithReason(syncIOError)`. Sources: [packages/next/src/server/node-environment-extensions/io-utils.tsx:58-77](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L58-L77)

Sources: [packages/next/src/server/node-environment-extensions/io-utils.tsx:13-103](https://github.com/blade47/next.js/blob/main/packages/next/src/server/node-environment-extensions/io-utils.tsx#L13-L103)

> [!CAUTION]
> During `EarlyRuntime` stages, synchronous I/O throws an error because the segment is runtime-prefetchable and an interruption would abort the prefetch prematurely, whereas `Runtime` stages permit synchronous I/O since non-prefetchable segments will never be runtime prefetched.

Sources: [packages/next/src/server/app-render/staged-rendering.ts:168-177](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/staged-rendering.ts#L168-L177)

### Sync IO API Types and Diagnostic Messages

Synchronous I/O tracking categorizes operations into specific API types, mapping each to dedicated documentation URLs and remediation guidance.

| SyncIOApiType | Documentation Record Keys | Associated Remediation Docs |
| :--- | :--- | :--- |
| `time` | `SYNC_IO_DOCS.time`, `SYNC_IO_CLIENT_DOCS.time`, `SYNC_IO_RUNTIME_DOCS.time` | `blocking-prerender-current-time` (+ telemetry bullet for `performance.now()`) |
| `random` | `SYNC_IO_DOCS.random`, `SYNC_IO_CLIENT_DOCS.random`, `SYNC_IO_RUNTIME_DOCS.random` | `blocking-prerender-random` |
| `crypto` | `SYNC_IO_DOCS.crypto`, `SYNC_IO_CLIENT_DOCS.crypto`, `SYNC_IO_RUNTIME_DOCS.crypto` | `blocking-prerender-crypto` |

Sources: [packages/next/src/server/app-render/sync-io-messages.ts:1-19](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/sync-io-messages.ts#L1-L19), [packages/next/src/server/app-render/sync-io-messages.ts:21-46](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/sync-io-messages.ts#L21-L46)

> [!NOTE]
> Server-side sync I/O errors recommend fixing the issue by adding a dynamic data access like `await connection()`, caching the value with `"use cache"`, or moving rendering to a Client Component. Client-side sync I/O errors suggest wrapping in `<Suspense>` or moving the read into a `useEffect` or event handler.

Sources: [packages/next/src/server/app-render/sync-io-messages.ts:33-85](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/sync-io-messages.ts#L33-L85)

## Cache Coordination and Route Stream Generation

### Overview

Cache coordination and route stream generation unify `use-cache` semantics, resume data cache serialization, and Flight stream generation. When handling cached or dynamic data dependencies within Next.js application render workflows, cache entries are evaluated for expiration, stale times, and dynamic omission before being embedded into the React Server Components (RSC) payload or staged execution pipelines. Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:2837-2849](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2837-L2849), [packages/next/src/server/app-render/app-render.tsx:914-964](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L914-L964)

### Cache Expiration and Dynamic Omission Semantics

During static generation or prerendering, cache entries with a revalidate value of `0` or an expiration time under the dynamic expiration threshold are omitted from the static shell. This creates a dynamic hole filled during resume operations. Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:2851-2892](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2851-L2892)

| WorkUnitStore Type | Behavior on Short Expiry / Revalidate `0` | Cache Signal & Deferred Action | Sources |
| :--- | :--- | :--- | :--- |
| `prerender` | Omitted from static shell; creates dynamic hole for resume | Ends cache signal read; resolves shared cache result as `prerender-dynamic` with a hanging promise | [packages/next/src/server/use-cache/use-cache-wrapper.ts:2856-2892](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2856-L2892) |
| `request` | Deferred to runtime or dynamic stage in development mode | Ends cache signal read (if unended); awaits devtools IO-aware promise for current or dynamic stage | [packages/next/src/server/use-cache/use-cache-wrapper.ts:2893-2917](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2893-L2917) |
| `prerender-runtime`, `prerender-ppr`, `cache`, `unstable-cache`, etc. | Passes through without explicit static shell omission | No direct transformation; governed by enclosing context | [packages/next/src/server/use-cache/use-cache-wrapper.ts:2918-2929](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2918-L2929) |

Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:2856-2929](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2856-L2929)

> [!TIP]
> When a cache entry's `revalidate` is set to `0` or its expiration falls below `DYNAMIC_EXPIRE`, the system avoids generating static pages for such data, replacing them with hanging promises that resolve via resume data caches.

Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:2851-2892](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L2851-L2892)

### Staged Dynamic Flight Render and Resume Data Serialization

When production staged dynamic flight renders execute in Node.js streams, the request store initializes stale time trackers, stage controllers, vary params accumulators, and async API promises. If runtime prefetching is enabled via loader trees, a prerender resume data cache and cache signal are spawned. Sources: [packages/next/src/server/app-render/app-render.tsx:914-964](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L914-L964)

The staged flight render follows a precise sequential task execution pipeline using `runInSequentialTasks`:
1. `stageController.advanceStage(RenderStage.ShellStatic)` advances the stage. Sources: [packages/next/src/server/app-render/app-render.tsx:1032-1036](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1032-L1036)
2. `workUnitAsyncStorage.run(requestStore, renderToNodeFlightStream, ...)` generates the source node flight stream. Sources: [packages/next/src/server/app-render/app-render.tsx:1037-1044](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1037-L1044)
3. `new ReplayableNodeStream(sourceStream)` creates replay streams for dynamic and static outputs, counting shell and static stage bytes. Sources: [packages/next/src/server/app-render/app-render.tsx:1046-1055](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1046-L1055)
4. `stageController.advanceStage(RenderStage.Static)` moves execution into the static stage. Sources: [packages/next/src/server/app-render/app-render.tsx:1059-1061](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1059-L1061)
5. `staleTimeIterable.close()` and `finishAccumulatingVaryParams(requestStore.varyParamsAccumulator)` flush tracking data. Sources: [packages/next/src/server/app-render/app-render.tsx:1062-1070](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1062-L1070)
6. `stageController.advanceStage(RenderStage.Dynamic)` completes the progression into the dynamic stage. Sources: [packages/next/src/server/app-render/app-render.tsx:1071-1074](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1071-L1074)

Sources: [packages/next/src/server/app-render/app-render.tsx:1032-1074](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/app-render.tsx#L1032-L1074)

> [!WARNING]
> Postponed states and resume data caches serialize into string formats incorporating payload length identifiers. If fallback route params are present, replacements are serialized and prepended to ensure parameter interpolation during resumption.

Sources: [packages/next/src/server/app-render/postponed-state.ts:78-115](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/postponed-state.ts#L78-L115)

## Related

- [App Server Rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-router-rendering/app-server-rendering)
- [Prefetching and PPR](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-routing/prefetching-and-ppr)


## Sitemap

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