---
title: "Router Reducer"
description: "The router reducer is the centralized state engine for the client-side AppRouter, responsible for computing and transitioning the application's top-level router state across user actions, navigatio..."
last_updated: "2026-10-12T06:38:56.86852+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/router-reducer"
---

<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/router-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer.ts)
- [packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts)
- [packages/next/src/client/components/app-router-state.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-state.ts)
- [packages/next/src/client/components/app-router-instance.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-instance.ts)
- [packages/next/src/client/components/router-reducer/router-reducer-types.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer-types.ts)
- [packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts)
- [packages/next/src/client/components/app-router.tsx](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router.tsx)
- [packages/next/src/client/components/render-tree.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/render-tree.ts)
- [packages/next/src/client/components/sequential-router-queue.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/sequential-router-queue.ts)
- [packages/next/src/shared/lib/router/router.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/shared/lib/router/router.ts)
- [packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts)
- [packages/next/src/client/components/router-reducer/create-initial-router-state.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/create-initial-router-state.ts)
- [packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts)
- [packages/next/src/next-devtools/dev-overlay/shared.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/next-devtools/dev-overlay/shared.ts)
- [packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts)
- [packages/next/src/client/components/segment-cache/decode-server-response.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/decode-server-response.ts)
- [packages/next/src/client/components/use-action-queue.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/use-action-queue.ts)
- [packages/next/src/client/components/segment-cache/cache.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/segment-cache/cache.ts)
- [packages/next/src/client/components/router-reducer/reducers/hmr-refresh-reducer.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/hmr-refresh-reducer.ts)
- [packages/next/src/client/components/navigator.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/navigator.ts)
- [packages/next/src/client/components/router-reducer/reducers/committed-state.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/committed-state.ts)
- [packages/next/src/client/components/router-reducer/fetch-server-response.ts](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/fetch-server-response.ts)
</details>

## Overview

The router reducer is the centralized state engine for the client-side AppRouter, responsible for computing and transitioning the application's top-level router state across user actions, navigation events, and server responses. Operating through a deterministic dispatch loop, it handles incoming action payloads by routing them to specialized reducer functions such as `navigateReducer`, `serverPatchReducer`, `restoreReducer`, `refreshReducer`, `hmrRefreshReducer`, and `serverActionReducer`. By coordinating with the [Client Runtime](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/client-runtime), [Segment Cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/segment-cache), and action queues, the router reducer manages segment revalidations, history traversals, and state restoration without full page reloads, bridging the gap between browser history states and React component tree updates.

Sources: [packages/next/src/client/components/router-reducer/router-reducer.ts:20-58](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer.ts#L20-L58), [packages/next/src/client/components/router-reducer/router-reducer-types.ts:204-258](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer-types.ts#L204-L258)

## Action Dispatch and Reducer Routing

### Dispatch Loop and Reducer Routing

The client-side state engine processes incoming reducer actions through `clientReducer`, a deterministic dispatch function that examines `action.type` and delegates payload handling to specialized reducer modules [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router). When operating on the server (`typeof window === 'undefined'`), the router executes `serverReducer`, a no-op function that returns the state unchanged to optimize server bundle tree-shaking [Client Runtime](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/client-runtime).

```typescript
function clientReducer(
  state: ReadonlyReducerState,
  action: ReducerActions
): ReducerState {
  switch (action.type) {
    case ACTION_NAVIGATE: {
      return navigateReducer(state, action)
    }
    case ACTION_SERVER_PATCH: {
      return serverPatchReducer(state, action)
    }
    case ACTION_RESTORE: {
      return restoreReducer(state, action)
    }
    case ACTION_REFRESH: {
      return refreshReducer(state, action)
    }
    case ACTION_HMR_REFRESH: {
      if (process.env.NODE_ENV === 'development') {
        const { hmrRefreshReducer } =
          require('./reducers/hmr-refresh-reducer') as typeof import('./reducers/hmr-refresh-reducer')
        return hmrRefreshReducer(state, action)
      } else {
        throw new Error(
          'hmrRefresh can only be used in development mode. Please use refresh instead.'
        )
      }
    }
    case ACTION_SERVER_ACTION: {
      return serverActionReducer(state, action)
    }
    default:
      throw new Error('Unknown action')
  }
}
```
Sources: [packages/next/src/client/components/router-reducer/router-reducer.ts:23-58](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer.ts#L23-L58)

The supported action constants define the complete mutation surface handled by the routing subsystem. Each action type maps to a distinct navigation or update lifecycle event.

| Action Constant | String Value | Target Reducer / Handler | Purpose |
| :--- | :--- | :--- | :--- |
| `ACTION_REFRESH` | `'refresh'` | `refreshReducer` | Full page data refresh and root state update |
| `ACTION_NAVIGATE` | `'navigate'` | `navigateReducer` | Push or replace navigation to a target URL |
| `ACTION_RESTORE` | `'restore'` | `restoreReducer` | History popstate traversal or state sync |
| `ACTION_SERVER_PATCH` | `'server-patch'` | `serverPatchReducer` | Applying server Flight data patches to cache and tree |
| `ACTION_HMR_REFRESH` | `'hmr-refresh'` | `hmrRefreshReducer` | Development-only HMR boundary refresh |
| `ACTION_SERVER_ACTION` | `'server-action'` | `serverActionReducer` | Server Action dispatch, execution, and response serialization |

Sources: [packages/next/src/client/components/router-reducer/router-reducer-types.ts:8-13](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer-types.ts#L8-L13), [packages/next/src/client/components/router-reducer/router-reducer.ts:27-53](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer.ts#L27-L53)

> [!NOTE]
> The `ACTION_HMR_REFRESH` case dynamically requires `hmrRefreshReducer` only when `process.env.NODE_ENV === 'development'`. In production builds, triggering this action throws an explicit error instructing callers to use `refresh` instead.

Sources: [packages/next/src/client/components/router-reducer/router-reducer.ts:40-50](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/router-reducer.ts#L40-L50)

### Operation Interface and Sequential Queue Coordination

User-facing operations originate in Navigator, which establishes safety preconditions and wraps dispatches inside React transitions before forwarding actions to the sequential action queue. The navigator exposes a unified function set (`navigate`, `push`, `replace`, `traverse`, `restore`, `refresh`, and `hmrRefresh`) implemented by `sequential-router-queue.ts`.

```typescript
export function navigate(
  href: string,
  navigateType: NavigateAction['navigateType'],
  scrollBehavior: ScrollBehavior,
  linkInstanceRef: LinkInstance | null,
  transitionTypes: string[] | undefined,
  prefetchIntent: RouterTransitionPrefetchIntent | null
): void {
  if (isJavaScriptURLString(href)) {
    throw new Error(
      'Next.js has blocked a javascript: URL as a security precaution.'
    )
  }

  startTransition(() => {
    if (transitionTypes) {
      for (const type of transitionTypes) {
        addTransitionType(type)
      }
    }

    const url = new URL(addBasePath(href), location.href)
    if (process.env.__NEXT_APP_NAV_FAIL_HANDLING) {
      window.next.__pendingUrl = url
    }

    setLinkForCurrentNavigation(linkInstanceRef)
    startRouterTransition(
      href,
      navigateType,
      getRequiredAppRouterState().tree,
      prefetchIntent
    )

    dispatchAppRouterAction({
      type: ACTION_NAVIGATE,
      url,
      isExternalUrl: isExternalURL(url),
      locationSearch: location.search,
      scrollBehavior,
      navigateType,
    })
  })
}
```
Sources: [packages/next/src/client/components/sequential-router-queue.ts:47-93](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/sequential-router-queue.ts#L47-L93)

> [!WARNING]
> Security checks inside `navigate` block any `javascript:` URL strings immediately before starting transitions, throwing an error to prevent script injection vulnerabilities [Client Runtime](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/client-runtime).

Sources: [packages/next/src/client/components/sequential-router-queue.ts:55-59](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/sequential-router-queue.ts#L55-L59)

Development-mode HMR refreshes manage an active `AbortController` generation tracker when `__NEXT_SERVER_COMPONENTS_HMR_CANCELLATION` is enabled. This tears down superseded fetch requests prior to dispatching new HMR refresh actions.

```typescript
export function hmrRefresh(): void {
  if (process.env.NODE_ENV !== 'development') {
    throw new Error(
      'hmrRefresh can only be used in development mode. Please use refresh instead.'
    )
  } else {
    resetKnownRoutes()
    let signal: AbortSignal | undefined
    if (process.env.__NEXT_SERVER_COMPONENTS_HMR_CANCELLATION) {
      activeHmrRefreshController?.abort()
      activeHmrRefreshController = new AbortController()
      signal = activeHmrRefreshController.signal
    }
    startTransition(() => {
      dispatchAppRouterAction({
        type: ACTION_HMR_REFRESH,
        signal,
      })
    })
  }
}
```
Sources: [packages/next/src/client/components/sequential-router-queue.ts:168-192](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/sequential-router-queue.ts#L168-L192)

## Initial Router State Hydration

### Segment Route Trees and Seed Decoding

Client hydration bootstraps the application router state by extracting parameters from `InitialRSCPayload` [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router). The initial transport tree is converted into a flight router state using `transportNodeToFlightRouterState`, and the canonical URL is resolved from browser `location` or parsed parts.

```typescript
export function createInitialRouterState({
  navigatedAt,
  initialRSCPayload,
  initialFlightStreamForCache,
  location,
}: InitialRouterStateParameters): AppRouterState {
  const {
    c: initialCanonicalUrlParts,
    t: initialTransportData,
    q: initialRenderedSearch,
    i: initialCouldBeIntercepted,
    S: initialSupportsPerSegmentPrefetching,
    s: initialStaleTime,
    l: initialStaticStageByteLength,
    r: initialRootVaryParams,
    p: initialRuntimePrefetchStream,
    d: initialDynamicStaleTimeSeconds,
  } = initialRSCPayload

  // When initialized on the server, the canonical URL is provided as an array of parts.
  // This is to ensure that when the RSC payload streamed to the client, crawlers don't interpret it
  // as a URL that should be crawled.
  const initialCanonicalUrl = initialCanonicalUrlParts.join('/')

  // The initial router state tree, derived from the transport tree.
  const initialTree = transportNodeToFlightRouterState(
    initialTransportData.t,
    initialRenderedSearch
  )

```
Sources: [packages/next/src/client/components/router-reducer/create-initial-router-state.ts:28-57](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/create-initial-router-state.ts#L28-L57)

`createNavigationSeed` decodes initial transport data into a `RouteTree` structure with embedded render outputs and heads. When executing in the browser (`location !== null`), the client registers known routes via `discoverKnownRoute` and schedules segment cache population based on the stale time configuration.

```typescript
const initialSeed = createNavigationSeed(
  navigatedAt,
  // There's no base tree to overlay onto; the initial payload is a full
  // render from the root.
  null,
  initialTransportData,
  // The initial payload may still be streaming in while we hydrate, so its
  // vary params can't be drained here; they decode as null. The
  // segment-cache write below re-decodes the transport data with the
  // payload's root params once the stale time has resolved.
  null,
  // Same for partiality: only segment-cache writes consume it, and the
  // write below re-decodes with the payload's actual response-level value.
  // Pass the conservative value here.
  true,
  // The initial payload always includes the param values in the tree
  // (fallback shells are patched with the parsed values before this runs —
  // see createInitialRSCPayloadFromFallbackPrerender), so there's no
  // pathname to parse them from.
  null,
  initialRenderedSearch,
  null,
  initialDynamicStaleTimeSeconds ?? UnknownDynamicStaleTime
)
const initialRoot = initialSeed.root
const initialNavigation = createInitialRenderTreeForHydration(
  navigatedAt,
  initialRoot,
  initialSeed.dynamicStaleAt
)

// The following only applies in the browser (location !== null) since neither
// route learning nor segment cache state persists from SSR to client.
if (location !== null) {
  // Learn the route pattern so we can predict it for future navigations.
  discoverKnownRoute(
    Date.now(),
    location.pathname,
    location.search as NormalizedSearch,
    null, // nextUrl — initial render is never an interception
    null, // No pending entry
    initialRoot,
    initialCouldBeIntercepted,
    canonicalUrl,
    initialSeed.renderedSearch,
    initialSupportsPerSegmentPrefetching,
    false // hasDynamicRewrite
  )
```
Sources: [packages/next/src/client/components/router-reducer/create-initial-router-state.ts:77-124](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/create-initial-router-state.ts#L77-L124)

> [!NOTE]
> For statically generated build-time HTML pages, trees baked into the initial RSC payload may lack correct segment inlining hints. The server marks these with `InliningHintsStale`, expiring the route cache entry so subsequent prefetches retrieve corrected hints from `/_tree`.

Sources: [packages/next/src/client/components/router-reducer/create-initial-router-state.ts:72-76](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/create-initial-router-state.ts#L72-L76)

### Static and Runtime Stream Caching

Depending on whether `initialStaticStageByteLength` is present alongside `initialFlightStreamForCache`, the hydration logic branches between partially static and fully static payloads to populate segment caches [Segment Cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/segment-cache).

| Payload Condition | Byte Length & Stream Checks | Caching Behavior |
| :--- | :--- | :--- |
| **Partially Static Page** | `initialStaticStageByteLength !== undefined` && `initialFlightStreamForCache != null` | Truncates cloned Flight stream at the static stage boundary, decodes via `decodeStageUntilBoundary`, and writes via `spawnStaticStageCacheWrite`. |
| **Fully Static Page** | `initialStaticStageByteLength === undefined` | Synthesizes a transport subset (`t`, `r`, `s`) directly from already decoded hydration data and writes to cache without re-decoding. |
| **No Caching / Unused** | `initialStaleTime === undefined` | Cancels `initialFlightStreamForCache` immediately. |

```typescript
if (initialStaleTime !== undefined) {
  if (
    initialStaticStageByteLength !== undefined &&
    initialFlightStreamForCache != null
  ) {
    // Partially static page — truncate the cloned Flight stream at the
    // static stage byte boundary, decode, and cache the static subset.
    // Promise.resolve wraps the Flight-deserialized thenable into a
    // native Promise so we can chain `.then` on it safely.
    Promise.resolve(initialStaticStageByteLength)
      .then(async (byteLength) => {
        if (byteLength === 0) {
          initialFlightStreamForCache.cancel()
          return
        }
        const staticStageResponse =
          await decodeStageUntilBoundary<InitialRSCPayload>(
            initialFlightStreamForCache,
            byteLength,
            undefined
          )
        if (staticStageResponse === null) {
          return
        }
        spawnStaticStageCacheWrite(
          Date.now(),
          staticStageResponse,
          true, // isResponsePartial
          null, // responseHeaders — no build-id check for initial HTML
          initialTree,
          initialRenderedSearch,
          segmentCacheMap // hydration writes are bound to the shared map
        )
      })
      .catch(() => {
        // The static stage processing failed. Not fatal — the page
        // rendered normally, we just won't write into the cache.
      })
  } else {
    // Fully static page — cache the initial payload's segment data as-is.
    // We're not using the initial response here (which would allow us to
    // combine the two branches) to avoid unnecessary decoding of the
    // Flight data, since we can just take the segment data that we
    // already decoded during hydration and write it into the
    // cache directly.
    spawnStaticStageCacheWrite(
      Date.now(),
      // The transport subset of the initial payload, already decoded
      // during hydration. `u` (the runtime-data verdict) is deliberately
      // omitted from this synthesized subset — its writes record their
      // strategy unrefined — while the truncated branch above forwards
      // the decoded payload's own `u`.
      {
        t: initialTransportData,
        r: initialRootVaryParams,
        s: initialStaleTime,
      },
      false, // isResponsePartial
      null, // responseHeaders — no build-id check for initial HTML
      initialTree,
      initialRenderedSearch,
      segmentCacheMap // hydration writes are bound to the shared map
    )

    // Cancel the stream clone — fully static path doesn't need it.
    initialFlightStreamForCache?.cancel()
  }
} else {
  // No caching — cancel the unused stream clone.
  initialFlightStreamForCache?.cancel()
}

```
Sources: [packages/next/src/client/components/router-reducer/create-initial-router-state.ts:133-204](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/create-initial-router-state.ts#L133-L204)

> [!WARNING]
> Hydration writes are strictly bound to `segmentCacheMap`. If runtime prefetch streams are included (`initialRuntimePrefetchStream != null`), `writeRuntimePrefetchStreamIntoCache` is invoked asynchronously during initialization but failures remain non-fatal.

Sources: [packages/next/src/client/components/router-reducer/create-initial-router-state.ts:209-221](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/create-initial-router-state.ts#L209-L221)

## Navigation Transitions and Route Trees

### Handling Client Navigation Actions

The `navigateReducer` processes incoming `NavigateAction` payloads [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router). It checks whether the target URL is external or if a meta page redirect tag (`__next-page-redirect`) is present in the document. When either condition matches, it immediately triggers a hard multi-page application navigation using `completeHardNavigation`.

```typescript
export function navigateReducer(
  state: ReadonlyReducerState,
  action: NavigateAction
): ReducerState {
  const { url, isExternalUrl, navigateType, scrollBehavior } = action

  if (isExternalUrl) {
    return completeHardNavigation(state, url, navigateType)
  }

  // Handles case where `<meta http-equiv="refresh">` tag is present,
  // which will trigger an MPA navigation.
  if (document.getElementById('__next-page-redirect')) {
    return completeHardNavigation(state, url, navigateType)
  }

```
Sources: [packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts:23-38](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts#L38)

When processing standard soft navigations, `navigateReducer` extracts the current URL from `state.canonicalUrl` and delegates to `navigate` from `app-router-state.ts` with `FreshnessPolicy.Default` [Segment Cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/segment-cache).

```typescript
  // Temporary glue code between the router reducer and the new navigation
  // implementation. Eventually we'll rewrite the router reducer to a
  // state machine.
  const currentUrl = new URL(state.canonicalUrl, location.origin)
  const currentRenderedSearch = state.renderedSearch
  return navigateUsingSegmentCache(
    state,
    url,
    currentUrl,
    currentRenderedSearch,
    state.root,
    state.tree,
    state.nextUrl,
    FreshnessPolicy.Default,
    scrollBehavior,
    navigateType
  )
}
```
Sources: [packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts:39-56](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts#L39-L56)

> [!WARNING]
> Security checks block any `javascript:` URL strings during hard navigation resolution, logging an error and returning the current router state unmodified without navigating.

Sources: [packages/next/src/client/components/app-router-state.ts:630-636](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-state.ts#L630-L636)

### Stale Time Verification and Constants

Stale time parameters govern how long cached route data and segment responses remain valid before requiring revalidation. These constants are initialized from environment configuration variables defined by `define-env-plugin` based on `nextConfig.experimental.staleTimes`.

| Constant Name | Source Expression | Default Behavior |
| :--- | :--- | :--- |
| `DYNAMIC_STALETIME_MS` | `Number(process.env.__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME) * 1000` | Defaults to 0 seconds for dynamic routes. |
| `STATIC_STALETIME_MS` | `getStaleTimeMs(Number(process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME))` | Defaults to 5 minutes for static routes. |

```typescript
export const DYNAMIC_STALETIME_MS =
  Number(process.env.__NEXT_CLIENT_ROUTER_DYNAMIC_STALETIME) * 1000

export const STATIC_STALETIME_MS = getStaleTimeMs(
  Number(process.env.__NEXT_CLIENT_ROUTER_STATIC_STALETIME)
)
```
Sources: [packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts:16-21](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts#L16-L21)

### Transitioning Router State Between Route Trees

When executing `navigate`, the router checks if testing APIs are enabled via `process.env.__NEXT_EXPOSE_TESTING_API`. If navigation locking is active, `beginLockedNavigation` creates a `NavigationLock` and forces an initial prefetch validation step before continuing.

```typescript
if (process.env.__NEXT_EXPOSE_TESTING_API) {
  const { isNavigationLocked } =
    require('./segment-cache/navigation-testing-lock') as typeof import('./segment-cache/navigation-testing-lock')
  if (isNavigationLocked()) {
    // Signal that a new locked navigation is starting. This force-resolves the
    // previous locked navigation's withheld data (so a reused shared segment
    // no longer carries a pending deferred rsc) and returns this navigation's
    // own withheld-data gate.
    navigationLock = beginLockedNavigation()
    return ensurePrefetchThenNavigate(
      state,
      url,
      currentUrl,
      currentRenderedSearch,
      currentRoot,
      currentFlightRouterState,
      nextUrl,
      freshnessPolicy,
      scrollBehavior,
      navigateType,
      navigationLock
    )
  }
}
```
Sources: [packages/next/src/client/components/app-router-state.ts:81-104](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-state.ts#L81-L104)

`navigateImpl` evaluates cache keys using `createCacheKey` and reads route cache entries via `readRouteCacheEntry`. If a fulfilled entry is found, it calls `navigateUsingPrefetchedRouteTree`. Otherwise, if optimistic routing is disabled (`process.env.__NEXT_OPTIMISTIC_ROUTING`), it attempts to construct an optimistic route entry using `deprecated_requestOptimisticRouteCacheEntry`.

```typescript
function navigateImpl(
  state: AppRouterState,
  url: URL,
  currentUrl: URL,
  currentRenderedSearch: string,
  currentRoot: RootRouteTree<CacheNode>,
  currentFlightRouterState: FlightRouterState,
  nextUrl: string | null,
  freshnessPolicy: FreshnessPolicy,
  scrollBehavior: ScrollBehavior,
  navigateType: 'push' | 'replace',
  navigationLock: NavigationLock | null,
  // The segment cache map this navigation is bound to: a locked navigation's
  // driving-task map, or the shared map. See `segmentCacheMap` in cache.ts.
  map: CacheMap<SegmentCacheEntry>
): AppRouterState | Promise<AppRouterState> {
  const now = Date.now()
  const href = url.href

  const cacheKey = createCacheKey(href, nextUrl)
  const route = readRouteCacheEntry(now, cacheKey)
  if (route !== null && route.status === EntryStatus.Fulfilled) {
    // We have a matching prefetch.
    return navigateUsingPrefetchedRouteTree(
      now,
      state,
      url,
      currentUrl,
      currentRenderedSearch,
      nextUrl,
      currentRoot,
      freshnessPolicy,
      scrollBehavior,
      navigateType,
      route,
      navigationLock,
      map
    )
  }

  // There was no matching route tree in the cache. Let's see if we can
  // construct an "optimistic" route tree using the deprecated search-params
  // based matching. This is only used when the new optimisticRouting flag is
  // disabled.
  //
  // Do not construct an optimistic route tree if there was a cache hit, but
  // the entry has a rejected status, since it may have been rejected due to a
  // rewrite or redirect based on the search params.
  //
  // TODO: There are multiple reasons a prefetch might be rejected; we should
  // track them explicitly and choose what to do here based on that.
  if (!process.env.__NEXT_OPTIMISTIC_ROUTING) {
    if (route === null || route.status !== EntryStatus.Rejected) {
      const optimisticRoute = deprecated_requestOptimisticRouteCacheEntry(
        now,
        url,
        nextUrl
      )
      if (optimisticRoute !== null) {
```
Sources: [packages/next/src/client/components/app-router-state.ts:123-181](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-state.ts#L123-L181)

During soft navigation completion in `completeSoftNavigation`, the router computes path alterations between the previous tree and incoming navigation tree using `computeChangedPath` to update interception parameters (`nextUrl` and `previousNextUrl`).

```typescript
export function completeSoftNavigation(
  oldState: AppRouterState,
  url: URL,
  referringNextUrl: string | null,
  navigation: RootNavigationTask,
  renderedSearch: string,
  canonicalUrl: string,
  navigateType: 'push' | 'replace',
  scrollBehavior: ScrollBehavior,
  scrollRef: ScrollRef | null,
  collectedDebugInfo: Array<unknown> | null
) {
  // The "Next-Url" is a special representation of the URL that Next.js
  // uses to implement interception routes.
  // TODO: Get rid of this extra traversal by computing this during the
  // same traversal that computes the tree itself. We should also figure out
  // what is the minimum information needed for the server to correctly
  // intercept the route.
  const tree = navigation.tree.route
  const changedPath = computeChangedPath(oldState.tree, tree)
  const nextUrlForNewRoute = changedPath ? changedPath : oldState.nextUrl

  // This value is stored on the state as `previousNextUrl`; the naming is
  // confusing. What it represents is the "Next-Url" header that was used to
  // fetch the incoming route. It's essentially the refererer URL, but in a
  // Next.js specific format. During refreshes, this is sent back to the server
  // instead of the current route's "Next-Url" so that the same interception
  // logic is applied as during the original navigation.
  const previousNextUrl = referringNextUrl

```
Sources: [packages/next/src/client/components/app-router-state.ts:660-689](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-state.ts#L660-L689)

> [!TIP]
> `completeSoftNavigation` inspects whether an incoming navigation constitutes solely a hash fragment modification by comparing pathnames and query search parameters against the old URL instance.

Sources: [packages/next/src/client/components/app-router-state.ts:690-700](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/app-router-state.ts#L690-L700)

## Cache Invalidation and Refresh Operations

### Segment Caching and Refresh Reducer Execution

When `refreshReducer` executes, it handles general refresh actions by clearing segment entries via `invalidateSegmentCacheEntries` unless bypassed by testing flags, while preserving the structural route cache. It then invokes `refreshDynamicData` using `FreshnessPolicy.RefreshAll` to re-fetch dynamic RSC data.

```typescript
export function refreshReducer(
  state: ReadonlyReducerState,
  action: RefreshAction
): ReducerState {
  // During a refresh, we invalidate the segment cache but not the route cache.
  // The route cache contains the tree structure (which segments exist at a
  // given URL) which doesn't change during a refresh. The segment cache
  // contains the actual RSC data which needs to be re-fetched.
  //
  // The Instant Navigation Testing API can bypass cache invalidation to
  // preserve prefetched data when refreshing after an MPA navigation. This is
  // only used for testing and is not exposed in production builds by default.
  const bypassCacheInvalidation =
    process.env.__NEXT_EXPOSE_TESTING_API && action.bypassCacheInvalidation
  if (!bypassCacheInvalidation) {
    const currentNextUrl = state.nextUrl
    const currentRoot = state.root
    invalidateSegmentCacheEntries(currentNextUrl, currentRoot)
  }
  // A full refresh has no HMR generation to cancel.
  return refreshDynamicData(state, FreshnessPolicy.RefreshAll, undefined)
}
```
Sources: [packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts:20-41](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts#L20-L41)

> [!NOTE]
> The Instant Navigation Testing API controlled by `process.env.__NEXT_EXPOSE_TESTING_API` and `action.bypassCacheInvalidation` allows skipping cache invalidation to preserve prefetched data during multi-page application (MPA) testing refreshes.

Sources: [packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts:29-38](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts#L29-L38)

### Dynamic Data Re-fetching and Navigation Seeds

`refreshDynamicData` invalidates the back-forward cache (`invalidateBfCache`) and checks for interception routes within the tree using `hasInterceptionRouteInCurrentTree` to decide whether to reuse `state.previousNextUrl` or `currentNextUrl`. It builds a `NavigationSeed` via `createNavigationSeed` and executes `navigateToKnownRoute` with `ScrollBehavior.NoScroll`.

```typescript
export function refreshDynamicData(
  state: ReadonlyReducerState,
  freshnessPolicy: FreshnessPolicy.RefreshAll | FreshnessPolicy.HMRRefresh,
  signal: AbortSignal | undefined
): ReducerState {
  // During a refresh, invalidate the BFCache, which may contain dynamic data.
  invalidateBfCache()

  const currentNextUrl = state.nextUrl

  // We always send the last next-url, not the current when performing a dynamic
  // request. This is because we update the next-url after a navigation, but we
  // want the same interception route to be matched that used the last next-url.
  const nextUrlForRefresh = hasInterceptionRouteInCurrentTree(state.tree)
    ? state.previousNextUrl || currentNextUrl
    : null

  // A refresh is modeled as a navigation to the current URL, but where any
  // existing dynamic data (including in shared layouts) is re-fetched.
  const currentCanonicalUrl = state.canonicalUrl
  const currentUrl = new URL(currentCanonicalUrl, location.origin)
  const currentRenderedSearch = state.renderedSearch
  const currentFlightRouterState = state.tree
  const scrollBehavior = ScrollBehavior.NoScroll
  const navigationLock = getCurrentNavigationLock()

  // Create a NavigationSeed from the current FlightRouterState.
  // TODO: Eventually we will store this type directly on the state object
  // instead of reconstructing it on demand. Part of a larger series of
  // refactors to unify the various tree types that the client deals with.
  const now = Date.now()
  // TODO: Store the dynamic stale time on the top-level state so it's known
  // during restores and refreshes.
  const refreshSeed = createNavigationSeed(
    now,
    currentFlightRouterState,
    // No transport data (and so no vary params, no partiality, and no
    // pathname to parse params from) — this converts the base tree alone.
    null,
    null,
    true,
    null,
    currentRenderedSearch,
    null,
    UnknownDynamicStaleTime
  )

  // If the previous navigation hasn't pushed its history entry yet (React
  // hasn't committed its state), this refresh may commit in its place, so it
  // takes over the push. If the navigation does commit first, HistoryUpdater
  // sees that the URL already matches and replaces instead.
  const navigateType = state.pushRef.pendingPush ? 'push' : 'replace'
  return navigateToKnownRoute(
    now,
    state,
    currentUrl,
    currentCanonicalUrl,
    refreshSeed,
    currentUrl,
    currentRenderedSearch,
    state.root,
    freshnessPolicy,
    nextUrlForRefresh,
    scrollBehavior,
    navigateType,
    navigationLock,
    // A refresh is bound to the shared map.
    segmentCacheMap,
    null,
    // Refresh navigations don't use route prediction, so there's no route
    // cache entry to mark as having a dynamic rewrite on mismatch. If a
    // mismatch occurs, the retry handler will traverse the known route tree
    // to find and mark the entry.
    null,
    signal
  )
```
Sources: [packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts:43-118](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts#L43-L118)

> [!WARNING]
> If a prior navigation has not yet committed its state (`state.pushRef.pendingPush`), the refresh reducer action takes over the history push operation; otherwise, `HistoryUpdater` replaces the entry if the URL matches.

Sources: [packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts:90-94](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts#L90-L94)

### Hot Module Replacement (HMR) Refresh Cycles

The `hmrRefreshReducer` function processes HMR refresh actions, inspecting `action.signal` to verify whether an incoming HMR generation was canceled or superseded while waiting behind [server actions](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/server-actions) in the router queue. If the signal has aborted, it exits early; otherwise, it delegates to `refreshDynamicData` using `FreshnessPolicy.HMRRefresh`.

```typescript
export function hmrRefreshReducer(
  state: ReadonlyReducerState,
  action: HmrRefreshAction
): ReducerState {
  // HMR actions may wait behind a Server Action in the router queue. If a newer
  // generation superseded this one before it started, don't install a refresh
  // tree whose request is already canceled.
  if (action.signal?.aborted) {
    return state
  }

  return refreshDynamicData(state, FreshnessPolicy.HMRRefresh, action.signal)
}
```
Sources: [packages/next/src/client/components/router-reducer/reducers/hmr-refresh-reducer.ts:9-21](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/hmr-refresh-reducer.ts#L9-L21)

## History Traversal and State Restoration

### Restoring Previous Router States

When the browser triggers history traversal events such as popstate, `restoreReducer` processes `RestoreAction` objects to reconstruct prior router states. It inspects `action.historyState` to recover the serialized `FlightRouterState` tree and rendered search parameters. If a history entry lacks this tree—such as entries created before hydration or anchor-link hash navigation—the reducer falls back to the existing `state.tree` to avoid breaking the router state machine.

```typescript
export function restoreReducer(
  state: ReadonlyReducerState,
  action: RestoreAction
): ReducerState {
  // This action is used to restore the router state from the history state.
  // However, it's possible that the history state no longer contains the `FlightRouterState`.
  // We will copy over the internal state on pushState/replaceState events, but if a history entry
  // occurred before hydration, or if the user navigated to a hash using a regular anchor link,
  // the history state will not contain the `FlightRouterState`.
  // In this case, we'll continue to use the existing tree so the router doesn't get into an invalid state.
  let treeToRestore: FlightRouterState | undefined
  let renderedSearch: string | undefined
  const historyState = action.historyState
  if (historyState) {
    treeToRestore = historyState.tree
    renderedSearch = historyState.renderedSearch
  } else {
    treeToRestore = state.tree
    renderedSearch = state.renderedSearch
  }
```
Sources: [packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts:23-42](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts#L22-L42)

> [!NOTE]
> `restoreReducer` calculates the restored next URL using `extractPathFromFlightRouterState(treeToRestore)`, falling back to `restoredUrl.pathname` if no interception path exists within the restored tree.

Sources: [packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts:45-48](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts#L45-L48)

### Synchronizing Segment Cache and Traversal Seeds

To re-establish traversal state, `restoreReducer` creates a navigation seed via `createNavigationSeed` utilizing `UnknownDynamicStaleTime` and the restored search parameters. It initiates a PPR navigation with `FreshnessPolicy.HistoryTraversal`, bound directly to the shared `segmentCacheMap` without any shell restriction.

```typescript
const restoreSeed = createNavigationSeed(
  now,
  treeToRestore,
  // No transport data (and so no vary params, no partiality, and no
  // pathname to parse params from) — this converts the base tree alone.
  null,
  null,
  true,
  null,
  renderedSearch,
  null,
  UnknownDynamicStaleTime
)
const navigation = startPPRNavigation(
  now,
  currentUrl,
  state.renderedSearch,
  state.root,
  restoreSeed.root,
  FreshnessPolicy.HistoryTraversal,
  restoreSeed.dynamicStaleAt,
  false,
  accumulation,
  // A history-traversal restore is bound to the shared map.
  segmentCacheMap,
  // A history-traversal restore never restricts to the shell.
  false
)
```
Sources: [packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts:56-83](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts#L56-L83)

> [!CAUTION]
> If `startPPRNavigation` returns `null` during a history traversal, `restoreReducer` immediately aborts and falls back to a hard navigation via `completeHardNavigation(state, restoredUrl, 'replace')`.

Sources: [packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts:85-87](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts#L85-L87)

### Spawning Dynamic Requests and Resetting Locks

After establishing the navigation task, `spawnDynamicRequests` is invoked with a `'replace'` navigation type and an un-gated `null` navigation lock to dispatch requests for missing segments without withholding data behind testing locks. Under testing APIs, `resetNavigationLockToPending` resets the lock state to a fresh pending scope. Finally, `completeTraverseNavigation` updates the router state with the new traversal task.

```typescript
spawnDynamicRequests(
  navigation,
  restoredUrl,
  restoredNextUrl,
  FreshnessPolicy.HistoryTraversal,
  accumulation,
  // History traversal doesn't use route prediction, so there's no route
  // cache entry to mark as having a dynamic rewrite on mismatch. If a
  // mismatch occurs, the retry handler will traverse the known route tree
  // to find and mark the entry.
  null,
  // History traversal always uses 'replace'.
  'replace',
  // Instant Navigation Testing API: a traversal is not a capture. Spawn its
  // dynamic requests ungated (null lock) so they render from cache or fetch
  // normally rather than being withheld behind the lock.
  null,
  // A history-traversal restore is bound to the shared map.
  segmentCacheMap,
  // Not an HMR refresh, so there's no request generation to cancel.
  undefined
)
// Instant Navigation Testing API: a traversal resets the lock to a fresh
// pending scope — releasing any data withheld by prior forward navigations and
// returning the panel to "awaiting" — without ending the testing session.
// No-op when the testing API is disabled or no lock is held.
resetNavigationLockToPending()
return completeTraverseNavigation(
  state,
  restoredUrl,
  renderedSearch,
  navigation,
  restoredNextUrl
)
```
Sources: [packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts:88-121](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts#L88-L121)

> [!IMPORTANT]
> `lastCommittedTree` tracks the flight router state from the last state committed to browser history via `HistoryUpdater` insertion effects, allowing the server-patch subsystem to distinguish retries of uncommitted transitions.

Sources: [packages/next/src/client/components/router-reducer/reducers/committed-state.ts:1-15](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/committed-state.ts#L1-L15)

## Server Patch Application and Fetching

### Processing Server Patches and Retries

When a route mismatch or server patch action occurs, `serverPatchReducer` processes `ServerPatchAction` objects to handle retries. If `action.mpa` is true or `action.seed` is null, the reducer falls back immediately to a hard navigation via `completeHardNavigation`.

```typescript
const retryMpa = action.mpa
const retryUrl = new URL(action.url, location.origin)
const retrySeed = action.seed
const navigateType = action.navigateType
if (retryMpa || retrySeed === null) {
  // If the server did not send back data during the mismatch, fall back to
  // an MPA navigation.
  return completeHardNavigation(state, retryUrl, navigateType)
}
```
Sources: [packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts:25-33](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts#L25-L33)

> [!WARNING]
> If `action.previousTree` does not match `state.tree`, a more recent navigation has superseded the mismatch, prompting the reducer to abort the retry and trigger a `refreshReducer` update to evict stale dynamic data.

Sources: [packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts:36-41](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts#L36-L41)

### Streaming and Decoding Server Responses

`fetchServerResponse` handles network requests for Flight data, incorporating request headers such as `RSC_HEADER`, `NEXT_ROUTER_STATE_TREE_HEADER`, and `NEXT_URL`. Responses are processed via `processFetch`, which inspects the leading partiality byte when cache components are enabled.

```typescript
const headers: RequestHeaders = {
  // Enable flight response
  [RSC_HEADER]: '1',
  // Provide the current router state
  [NEXT_ROUTER_STATE_TREE_HEADER]: prepareFlightRouterStateForRequest(
    flightRouterState,
    options.isHmrRefresh
  ),
}

if (process.env.NODE_ENV === 'development' && options.isHmrRefresh) {
  headers[NEXT_HMR_REFRESH_HEADER] = '1'
}

if (nextUrl) {
  headers[NEXT_URL] = nextUrl
}

```
Sources: [packages/next/src/client/components/router-reducer/fetch-server-response.ts:152-169](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/fetch-server-response.ts#L152-L169)

> [!NOTE]
> When `__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS` is active, `processFetch` tees the response body multiple times to provision independent readers for the main Flight decoder, static-stage extractor, and shell-stage extractor.

Sources: [packages/next/src/client/components/router-reducer/fetch-server-response.ts:403-414](https://github.com/blade47/next.js/blob/d8c1c1b0002db8ccb63b58095f338e8bf423fa99/packages/next/src/client/components/router-reducer/fetch-server-response.ts#L403-L414)

## Related

- [App Router](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/app-router)
- [Segment Cache](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/client-runtime/segment-cache)
- [Server Actions](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-rendering/server-actions)


## Sitemap

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