---
title: "Async Request Context"
description: "Async Request Context serves as the underlying execution tracking and scoping mechanism in Next.js, managing asynchronous data flow across server rendering passes, server actions, route handlers, a..."
last_updated: "2026-09-23T10:52:03.111005+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/async-request-context"
---

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

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

- [packages/next/src/server/app-render/work-async-storage.external.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-async-storage.external.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/async-storage/request-store.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts)
- [packages/next/src/server/app-render/after-task-async-storage-instance.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/after-task-async-storage-instance.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/server/request/headers.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/headers.ts)
- [packages/next/src/server/app-render/work-unit-async-storage.external.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-unit-async-storage.external.ts)
- [packages/next/src/server/app-render/action-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/action-handler.ts)
- [packages/next/src/server/app-render/work-async-storage-instance.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-async-storage-instance.ts)
- [packages/next/src/server/app-render/work-unit-async-storage-instance.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-unit-async-storage-instance.ts)
- [packages/next/src/experimental/testmode/context.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/experimental/testmode/context.ts)
- [packages/next/src/server/request/cookies.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/cookies.ts)
- [packages/next/src/server/after/after-context.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts)
- [packages/next/src/server/app-render/after-task-async-storage.external.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/after-task-async-storage.external.ts)
- [packages/next/src/server/web/adapter.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/adapter.ts)
- [packages/next/src/server/async-storage/with-store.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/with-store.ts)
- [packages/next/src/server/after/after.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after.ts)
- [packages/next/src/server/after/builtin-request-context.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/builtin-request-context.ts)
- [packages/next/src/server/app-render/action-async-storage-instance.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/action-async-storage-instance.ts)
- [packages/next/src/server/async-storage/work-store.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/work-store.ts)
- [packages/next/src/server/web/spec-extension/adapters/request-cookies.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts)
- [packages/next/src/server/app-render/async-local-storage.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/async-local-storage.ts)
- [packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts)
- [packages/next/src/server/after/run-with-after.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/run-with-after.ts)
- [packages/next/src/server/app-render/console-async-storage-instance.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/console-async-storage-instance.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/server/app-render/action-async-storage.external.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/action-async-storage.external.ts)
- [packages/next/src/server/dev/use-cache-probe-worker.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/use-cache-probe-worker.ts)
- [packages/next/src/server/app-render/dynamic-access-async-storage.external.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-access-async-storage.external.ts)
- [packages/next/src/server/app-render/console-async-storage.external.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/console-async-storage.external.ts)
</details>

## Overview

Async Request Context serves as the underlying execution tracking and scoping mechanism in Next.js, managing asynchronous data flow across server rendering passes, server actions, route handlers, and background tasks. By leveraging Node.js `AsyncLocalStorage` alongside custom store implementations, it solves the problem of safely propagating request headers, cookies, render phases, and cache configurations down deeply nested component trees without relying on global mutable state or explicit prop drilling. 

Key design decisions separate global work parameters from per-request metadata and cached execution scopes, preventing state leaks between independent cache boundaries and prerender passes. It interacts closely with dynamic APIs such as `headers()` and `cookies()` to enforce phase-based mutation rules, track dynamic data access, and schedule deferred background execution via `after()`. Sources: [packages/next/src/server/app-render/work-async-storage.external.ts:17-151](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-async-storage.external.ts#L17-L151), [packages/next/src/server/use-cache/use-cache-wrapper.ts:620-626](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L620-L626), [packages/next/src/server/async-storage/request-store.ts:99-123](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L99-L123), [packages/next/src/server/request/headers.ts:42-56](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/headers.ts#L42-L56), [packages/next/src/server/request/cookies.ts:35-50](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/cookies.ts#L35-L50), [packages/next/src/server/after/after-context.ts:39-53](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts#L39-L53), [packages/next/src/server/web/adapter.ts:339-346](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/adapter.ts#L339-L346)

## AsyncLocalStorage Infrastructure and Store Hierarchy

### Overview

Next.js builds its asynchronous context mechanism on top of an abstraction layer that wraps Node.js `AsyncLocalStorage`. When `AsyncLocalStorage` is unavailable in a runtime environment, the infrastructure falls back to a `FakeAsyncLocalStorage` implementation that throws errors on store operations like `run()`, `disable()`, `exit()`, and `enterWith()`. The base factory function `createAsyncLocalStorage()` inspects `globalThis.AsyncLocalStorage` to instantiate either the native instance or the fallback variant.

Sources: [packages/next/src/server/app-render/async-local-storage.ts:7-46](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/async-local-storage.ts#L7-L46)

```typescript
const maybeGlobalAsyncLocalStorage =
  typeof globalThis !== 'undefined' && (globalThis as any).AsyncLocalStorage

export function createAsyncLocalStorage<
  Store extends {},
>(): AsyncLocalStorage<Store> {
  if (maybeGlobalAsyncLocalStorage) {
    return new maybeGlobalAsyncLocalStorage()
  }
  return new FakeAsyncLocalStorage()
}
```

Sources: [packages/next/src/server/app-render/async-local-storage.ts:36-46](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/async-local-storage.ts#L36-L46)

### Store Hierarchy and Definitions

The application rendering engine divides execution contexts across distinct store types. Each store instance wraps `AsyncLocalStorage` with a specialized interface defining its contextual properties.

| Store Instance Module | Interface Type | Store Properties / Shape |
| :--- | :--- | :--- |
| `action-async-storage-instance.ts` | `ActionAsyncStorage` | `readonly isAction?: boolean`, `readonly isAppRoute?: boolean` |
| `console-async-storage-instance.ts` | `ConsoleAsyncStorage` | `readonly dim: boolean` |
| `dynamic-access-async-storage-instance.ts` | `DynamicAccessStorage` | `readonly abortController: AbortController` |
| `work-async-storage-instance.ts` | `WorkAsyncStorage` | *Defined via external storage module* |
| `work-unit-async-storage-instance.ts` | `WorkUnitAsyncStorage` | *Defined via external storage module* |
| `after-task-async-storage-instance.ts` | `AfterTaskAsyncStorage` | *Defined via external storage module* |

Sources: [packages/next/src/server/app-render/action-async-storage.external.ts:5-10](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/action-async-storage.external.ts#L5-L10), [packages/next/src/server/app-render/console-async-storage.external.ts:6-15](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/console-async-storage.external.ts#L6-L15), [packages/next/src/server/app-render/dynamic-access-async-storage.external.ts:6-10](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-access-async-storage.external.ts#L6-L10), [packages/next/src/server/app-render/action-async-storage-instance.ts:4-6](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/action-async-storage-instance.ts#L4-L6), [packages/next/src/server/app-render/console-async-storage-instance.ts:4-6](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/console-async-storage-instance.ts#L4-L6), [packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts:4-6](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts#L4-L6)

> [!NOTE]
> `ConsoleStore` utilizes the `dim` property to control output coloring. When `dim` is set to true, log colors are dimmed to indicate that the log originates from a repeat or validation render that is irrelevant to the primary server action.

Sources: [packages/next/src/server/app-render/console-async-storage.external.ts:6-13](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/console-async-storage.external.ts#L6-L13)

### Snapshot Binding and Store Wrapping

To propagate execution contexts across asynchronous boundaries, helper utilities manage function binding and snapshot creation. `bindSnapshot` delegates to native `AsyncLocalStorage.bind()` or `FakeAsyncLocalStorage.bind()`, while `createSnapshot()` captures execution state or returns the identity function when native capabilities are absent. Sources: [packages/next/src/server/app-render/async-local-storage.ts:48-68](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/async-local-storage.ts#L48-L68)

```typescript
export function bindSnapshot<T>(
  fn: T
): T {
  if (maybeGlobalAsyncLocalStorage) {
    return maybeGlobalAsyncLocalStorage.bind(fn)
  }
  return FakeAsyncLocalStorage.bind(fn)
}

export function createSnapshot(): <R, TArgs extends any[]>(
  fn: (...args: TArgs) => R,
  ...args: TArgs
) => R {
  if (maybeGlobalAsyncLocalStorage) {
    return maybeGlobalAsyncLocalStorage.snapshot()
  }
  return function (fn: any, ...args: any[]) {
    return fn(...args)
  }
}
```

Sources: [packages/next/src/server/app-render/async-local-storage.ts:48-68](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/async-local-storage.ts#L48-L68)

Additionally, the generic `WithStore` type signature standardizes how storage implementations supply a context to callback functions:

```typescript
export type WithStore<Store extends {}, Context extends {}> = <Result>(
  storage: AsyncLocalStorage<Store>,
  context: Context,
  callback: (store: Store) => Result
) => Result
```

Sources: [packages/next/src/server/async-storage/with-store.ts:12-16](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/with-store.ts#L12-L16)

## RequestStore and HTTP Context Lifecycle

### Overview

The request store lifecycle centers on initializing per-request state, sanitizing incoming headers, binding request cookies, and bridging runtime adapters between Node.js request/response pairs and Edge handlers or probe workers. The `RequestStore` instance relies on decoupled inputs, allowing contexts to be instantiated without requiring a live Node.js `IncomingMessage` or `BaseNextRequest`. Sources: [packages/next/src/server/async-storage/request-store.ts:92-98](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L92-L98)

### Request Store Initialization and Headers Cleaning

When a request store is created for rendering via `createRequestStoreForRender`, it assigns a default phase of `'render'`, extracts headers from `req.headers`, and configures cookie update callbacks. The internal `getHeaders` helper processes raw headers using `HeadersAdapter.from(headers)` and strips internal plumbing headers such as `FLIGHT_HEADERS`, `NEXT_REQUEST_ID_HEADER`, and `NEXT_HTML_REQUEST_ID_HEADER` before sealing the headers instance. Sources: [packages/next/src/server/async-storage/request-store.ts:33-49](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L33-L49), [packages/next/src/server/async-storage/request-store.ts:158-174](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L158-L174)

```typescript
function getHeaders(headers: Headers | IncomingHttpHeaders): ReadonlyHeaders {
  const cleaned = HeadersAdapter.from(headers)
  for (const header of FLIGHT_HEADERS) {
    cleaned.delete(header)
  }

  cleaned.delete(NEXT_REQUEST_ID_HEADER)
  cleaned.delete(NEXT_HTML_REQUEST_ID_HEADER)

  return HeadersAdapter.seal(cleaned)
}
```

Sources: [packages/next/src/server/async-storage/request-store.ts:33-49](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L33-L49)

### Middleware Cookie Merging

If middleware sets cookies on a request via the `x-middleware-set-cookie` header, `mergeMiddlewareCookies` parses the cookie string using `splitCookiesString`, wraps them in a `ResponseCookies` container, and merges them into the existing request cookies object so that subsequent `cookies()` calls can access newly written cookies. Sources: [packages/next/src/server/async-storage/request-store.ts:130-156](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L130-L156)

```typescript
function mergeMiddlewareCookies(
  headers: Headers | IncomingHttpHeaders,
  existingCookies: RequestCookies | ResponseCookies
) {
  if (
    'x-middleware-set-cookie' in headers &&
    typeof headers['x-middleware-set-cookie'] === 'string'
  ) {
    const setCookieValue = headers['x-middleware-set-cookie']
    const responseHeaders = new Headers()

    for (const cookie of splitCookiesString(setCookieValue)) {
      responseHeaders.append('set-cookie', cookie)
    }

    const responseCookies = new ResponseCookies(responseHeaders)

    for (const cookie of responseCookies.getAll()) {
      existingCookies.set(cookie)
    }
  }
}
```

Sources: [packages/next/src/server/async-storage/request-store.ts:130-156](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L130-L156)

### Runtime Adapter Bridges and Worker Probes

In Edge runtimes and middleware adapters, execution bridges bind request stores using `createRequestStoreForAPI`. For example, `packages/next/src/server/web/adapter.ts` constructs implicit tags, wraps cookie updates, and runs the request and work async storage scopes around the middleware handler: Sources: [packages/next/src/server/web/adapter.ts:281-346](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/adapter.ts#L281-L346)

```typescript
const requestStore = createRequestStoreForAPI(
  request,
  request.nextUrl,
  implicitTags,
  onUpdateCookies,
  previewProps
)

return await workAsyncStorage.run(workStore, () =>
  workUnitAsyncStorage.run(
    requestStore,
    params.handler,
    request,
    event
  )
)
```

Sources: [packages/next/src/server/web/adapter.ts:294-346](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/adapter.ts#L294-L346)

Similarly, the cache probe worker (`use-cache-probe-worker.ts`) initializes a throwaway request store from a serializable request snapshot without an underlying Node.js socket, enabling isolated re-executions for `'use cache'` deadlock detection: Sources: [packages/next/src/server/dev/use-cache-probe-worker.ts:155-167](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/use-cache-probe-worker.ts#L155-L167)

```typescript
const workUnitStore = createRequestStore({
  phase: 'render',
  headers: new Headers(msg.request.headers),
  onUpdateCookies: undefined,
  url: { pathname: msg.request.urlPathname, search: msg.request.urlSearch },
  rootParams: msg.request.rootParams,
  implicitTags: { tags: [], expirationsByCacheKind: new Map() },
  resumeDataCache: null,
  previewProps: undefined,
  isHmrRefresh: msg.request.isHmrRefresh,
  serverComponentsHmrCache: undefined,
  fallbackParams: null,
})
```

Sources: [packages/next/src/server/dev/use-cache-probe-worker.ts:155-167](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/use-cache-probe-worker.ts#L155-L167)

| Request Store Input Property | Expected Type / Shape | Purpose in Request Lifecycle |
| :--- | :--- | :--- |
| `phase` | `RequestStore['phase']` | Defines the current execution phase (e.g., `'render'`) |
| `headers` | `Headers \| IncomingHttpHeaders` | Raw request headers before adapter sanitization |
| `onUpdateCookies` | `((cookies: string[]) => void) \| undefined` | Callback invoked when userspace mutates cookies |
| `url` | `{ pathname: string; search?: string }` | Identifies the pathname and search components of the request URL |
| `rootParams` | `Params` | Root route parameters for the active render |
| `implicitTags` | `ImplicitTags` | Cache tags implicitly associated with the request route |
| `resumeDataCache` | `ResumeDataCache \| null` | Cache used for streaming resume data during rendering |
| `previewProps` | `__ApiPreviewProps \| undefined` | Configuration properties for draft and preview modes |
| `isHmrRefresh` | `boolean \| undefined` | Flag indicating whether the request stems from a Hot Module Replacement refresh |
| `serverComponentsHmrCache` | `ServerComponentsHmrCache \| undefined` | Cache storage for Server Components during development HMR |
| `fallbackParams` | `OpaqueFallbackRouteParams \| null \| undefined` | Opaque fallback parameters for static paths |

Sources: [packages/next/src/server/async-storage/request-store.ts:99-123](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/request-store.ts#L99-L123)

## WorkStore and WorkUnitStore Architecture

### Overview

The Next.js rendering engine relies on a dual-store architecture managed via Node.js `AsyncLocalStorage` instances: `WorkStore` (via `workAsyncStorage`) and `WorkUnitStore` (via `workUnitAsyncStorage`). While `WorkStore` tracks top-level request and build configuration metadata across the entire render tree, `WorkUnitStore` encapsulates specific execution scopes such as incoming requests, cache boundaries (`"use cache"` or `unstable_cache`), prerenders, and static generation parameter sweeps. This separation ensures that request-specific state cannot leak into cached scopes. Sources: [packages/next/src/server/app-render/work-async-storage.external.ts:1-156](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-async-storage.external.ts#L1-L156), [packages/next/src/server/app-render/work-unit-async-storage.external.ts:369-426](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-unit-async-storage.external.ts#L369-L426)

### WorkStore Field Definitions and Static Generation Rules

`WorkStore` is initialized through `createWorkStore` and tracks global options, build identifiers, timeout configurations, and deduplication maps for fetch metrics and cache invocations. Sources: [packages/next/src/server/app-render/work-async-storage.external.ts:17-151](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-async-storage.external.ts#L17-L151), [packages/next/src/server/async-storage/work-store.ts:83-162](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/work-store.ts#L83-L162)

| WorkStore Property | Type | Purpose / Description |
| :--- | :--- | :--- |
| `isStaticGeneration` | `boolean` | Determines if the current pass is a static prerender. |
| `page` | `string` | File path relative to the page being rendered. |
| `route` | `string` | Normalized route path without trailing `/page` or `/route`. |
| `incrementalCache` | `IncrementalCache \| undefined` | Global or local incremental cache instance. |
| `useCacheTimeout` | `number` | Timeout limit in milliseconds for `"use cache"` executions. |
| `staticPageGenerationTimeout` | `number` | Timeout limit for static page generation. |
| `pendingCacheInvocations` | `Map<string, Promise<SharedCacheResult>>` | Intra-request deduplication map keyed by coarse cache key. |
| `runInCleanSnapshot` | `(<R, TArgs extends any[]>(fn: (...args: TArgs) => R, ...args: TArgs) => R)` | Executes functions inside a clean `AsyncLocalStorage` snapshot. |

Sources: [packages/next/src/server/app-render/work-async-storage.external.ts:17-151](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-async-storage.external.ts#L17-L151), [packages/next/src/server/async-storage/work-store.ts:83-162](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/work-store.ts#L83-L162)

The determination of static generation follows strict rules based on render options: Sources: [packages/next/src/server/async-storage/work-store.ts:109-114](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/work-store.ts#L109-L114)

```typescript
const isStaticGeneration =
  !renderOpts.shouldWaitOnAllReady &&
  !renderOpts.supportsDynamicResponse &&
  !renderOpts.isDraftMode &&
  !renderOpts.isPossibleServerAction
```

Sources: [packages/next/src/server/async-storage/work-store.ts:109-114](https://github.com/blade47/next.js/blob/main/packages/next/src/server/async-storage/work-store.ts#L109-L114)

### WorkUnitStore Variants and Cache Shadowing

`WorkUnitStore` is a union type representing different execution units. When entering a cache scope, `createUseCacheStore` constructs a `UseCacheStore` that shadows any outer request store, explicitly preventing the leakage of request-specific objects like unmasked cookies or headers while selectively copying required properties. Sources: [packages/next/src/server/app-render/work-unit-async-storage.external.ts:372-422](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-unit-async-storage.external.ts#L372-L422), [packages/next/src/server/use-cache/use-cache-wrapper.ts:640-720](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L640-L720)

| WorkUnitStore Type | Identifier String | Scope & Behavior |
| :--- | :--- | :--- |
| `RequestStore` | `'request'` | Represents an active HTTP request with cookies, headers, and resume cache. |
| `PublicUseCacheStore` | `'cache'` | Public `"use cache"` boundary isolating requests from cached output. |
| `PrivateUseCacheStore` | `'private-cache'` | Private cache boundary retaining scoped headers, cookies, and root parameters. |
| `UnstableCacheStore` | `'unstable-cache'` | Legacy `unstable_cache` scope where root parameters are `undefined`. |
| `PrerenderStore` | `'prerender'`, `'prerender-ppr'`, etc. | Manages static prerendering, streaming, and staged rendering controllers. |
| `GenerateStaticParamsStore` | `'generate-static-params'` | Tracks generation parameters (`rootParams`) during static path generation. |

Sources: [packages/next/src/server/app-render/work-unit-async-storage.external.ts:372-422](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-unit-async-storage.external.ts#L372-L422), [packages/next/src/server/use-cache/use-cache-wrapper.ts:640-720](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L640-L720)

> [!WARNING]
> Inside an `UnstableCacheStore`, `rootParams` is always hardcoded as `undefined`. Any nested `"use cache"` function attempting to access route parameters in this context will encounter `undefined` and throw an error. Sources: [packages/next/src/server/app-render/work-unit-async-storage.external.ts:391-399](https://github.com/blade47/next.js/blob/main/packages/next/src/server/app-render/work-unit-async-storage.external.ts#L391-L399)

### Cache Generation Walkthrough and Clean Snapshots

When generating a cache entry, Next.js detaches from request-specific contexts by executing through a series of wrappers that clear and restore the storage layers: Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:585-638](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L585-L638)

`generateCacheEntry()` calls `workStore.runInCleanSnapshot()`, which invokes `generateCacheEntryWithRestoredWorkStore()`. This function resets the asynchronous context and binds the work store via `workAsyncStorage.run()`, before passing execution to `generateCacheEntryWithCacheContext()`: Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:585-638](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L585-L638)

```typescript
function generateCacheEntry(
  workStore: WorkStore,
  cacheContext: CacheContext,
  clientReferenceManifest: DeepReadonly<ClientReferenceManifest>,
  encodedArguments: FormData | string,
  fn: (...args: unknown[]) => Promise<unknown>,
  timeoutError: UseCacheTimeoutError,
  deadlockError: UseCacheDeadlockError | undefined
) {
  return workStore.runInCleanSnapshot(
    generateCacheEntryWithRestoredWorkStore,
    workStore,
    cacheContext,
    clientReferenceManifest,
    encodedArguments,
    fn,
    timeoutError,
    deadlockError
  )
}
```

Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:585-609](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L585-L609)

> [!NOTE]
> Request stores and prerender stores are explicitly excluded from cache generation snapshots. This guarantees that request-scoped elements such as `cookies()` inside a `React.cache()` invocation cannot leak into or contaminate cached outputs. Sources: [packages/next/src/server/use-cache/use-cache-wrapper.ts:620-626](https://github.com/blade47/next.js/blob/main/packages/next/src/server/use-cache/use-cache-wrapper.ts#L620-L626)

## Headers and Cookies Access Adapters

### Overview

The `headers()` and `cookies()` functions provide asynchronous access to incoming HTTP request headers and request-response cookie stores. These APIs integrate with asynchronous storage to enforce dynamic tracking, validate execution phases, and prevent synchronous access or improper usage across cache scopes and background callbacks. Sources: [packages/next/src/server/request/headers.ts:33-56](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/headers.ts#L33-L56), [packages/next/src/server/request/cookies.ts:35-50](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/cookies.ts#L35-L50)

### Phase-Based Cookie Mutability Enforcement

Cookies can only be modified when the request store is operating within specific lifecycle phases, such as during a Server Action. The `areCookiesMutableInCurrentPhase` function inspects the `requestStore.phase` property to determine whether mutation is permitted. Sources: [packages/next/src/server/web/spec-extension/adapters/request-cookies.ts:208-210](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts#L208-L210)

```typescript
export function areCookiesMutableInCurrentPhase(requestStore: RequestStore) {
  return requestStore.phase === 'action'
}
```

Sources: [packages/next/src/server/web/spec-extension/adapters/request-cookies.ts:208-210](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts#L208-L210)

When mutation methods like `set` or `delete` are invoked on `cookies()`, `createCookiesWithMutableAccessCheck` wraps the target store and triggers `ensureCookiesAreStillMutable()`. If the current phase has transitioned (such as moving from `action` to `render` or `render` to `after`), mutation attempts throw a `ReadonlyRequestCookiesError`. Sources: [packages/next/src/server/web/spec-extension/adapters/request-cookies.ts:181-227](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts#L181-L227)

> [!WARNING]
> Attempting to modify cookies via `cookies().set()` or `cookies().delete()` outside of a Server Action or Route Handler phase triggers `ReadonlyRequestCookiesError`, halting execution with an unmodifiable cookies error. Sources: [packages/next/src/server/web/spec-extension/adapters/request-cookies.ts:12-22](https://github.com/blade47/next.js/blob/main/packages/next/src/server/web/spec-extension/adapters/request-cookies.ts#L12-L22)

### Dynamic Tracking and Ergonomic Protections

Both `headers()` and `cookies()` return promises that resolve to read-only or mutable collections. To discourage synchronous access anti-patterns (such as calling properties directly on the returned promise), Next.js instruments the promise objects with warning descriptors in development mode. Sources: [packages/next/src/server/request/headers.ts:250-271](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/headers.ts#L250-L271), [packages/next/src/server/request/cookies.ts:259-278](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/cookies.ts#L259-L278)

| Instrumenting Function | Target Promise Type | Properties / Symbols Intercepted |
| :--- | :--- | :--- |
| `instrumentHeadersPromiseWithDevWarnings` | `Promise<ReadonlyHeaders>` | `Symbol.iterator`, `append`, `delete`, `get`, `has`, `set`, `getSetCookie`, `forEach`, `keys`, `values`, `entries` |
| `instrumentCookiesPromiseWithDevWarnings` | `Promise<ReadonlyRequestCookies>` | `Symbol.iterator`, `size`, `get`, `getAll`, `has`, `set`, `delete`, `clear`, `toString` |

Sources: [packages/next/src/server/request/headers.ts:250-271](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/headers.ts#L250-L271), [packages/next/src/server/request/cookies.ts:259-278](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/cookies.ts#L259-L278)

> [!NOTE]
> Synchronously accessing methods or properties on the unresolved `headers()` or `cookies()` promise in development invokes `createHeadersAccessError` or `createCookiesAccessError`, reminding developers to unwrap the promise using `await` or `React.use()`. Sources: [packages/next/src/server/request/headers.ts:317-327](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/headers.ts#L317-L327), [packages/next/src/server/request/cookies.ts:324-334](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request/cookies.ts#L324-L334)

## After-Response Tasks and Execution Context

### Overview

The `after()` API allows developers to schedule callbacks and promises to execute after the current request finishes processing. Managing this deferred work relies on the `AfterContext` class, `AfterRunner`, and the `afterTaskAsyncStorage` instance to preserve request execution contexts and manage task error handling across background boundaries. Sources: [packages/next/src/server/after/after.ts:6-21](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after.ts#L6-L21), [packages/next/src/server/after/after-context.ts:21-37](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts#L21-L37), [packages/next/src/server/after/run-with-after.ts:12-33](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/run-with-after.ts#L12-L33)

### Scheduling and Execution Flow

When an `after()` task is submitted, execution flows through validation and queue management steps. The following call chain illustrates how a task moves from invocation to execution: Sources: [packages/next/src/server/after/after.ts:9-20](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after.ts#L9-L20), [packages/next/src/server/after/after-context.ts:39-102](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts#L39-L102)

`after()` → `workAsyncStorage.getStore()` → `afterContext.after()` → `addCallback()` → `bindSnapshot()` → `afterTaskAsyncStorage.run()` → `callbackQueue.add()`

Sources: [packages/next/src/server/after/after.ts:9-20](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after.ts#L9-L20), [packages/next/src/server/after/after-context.ts:39-102](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts#L39-L102)

> [!WARNING]
> Calling `after()` outside of a request scope throws an error (`\`after\` was called outside a request scope`), as it requires an active `workStore` containing an initialized `afterContext`. Sources: [packages/next/src/server/after/after.ts:10-17](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after.ts#L10-L17)

### `AfterContext` Options and Properties

The behavior and lifecycle of deferred tasks are governed by configuration options passed into `AfterContext`. Sources: [packages/next/src/server/after/after-context.ts:15-37](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts#L15-L37)

| Option / Property | Type | Description |
| :--- | :--- | :--- |
| `waitUntil` | `RequestLifecycleOpts['waitUntil'] \| undefined` | Platform function to extend the lifetime of the request worker for promises and callback execution. |
| `onClose` | `RequestLifecycleOpts['onClose']` | Handler triggered when the request connection closes, initiating callback execution. |
| `onTaskError` | `RequestLifecycleOpts['onAfterTaskError'] \| undefined` | Custom error handler invoked if a background task or promise rejects. |
| `callbackQueue` | `PromiseQueue` | Internal queue managing callback execution order and concurrency. |
| `workUnitStores` | `Set<WorkUnitStore>` | Set tracking associated work unit stores whose phases transition to `'after'` during execution. |

Sources: [packages/next/src/server/after/after-context.ts:15-37](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts#L15-L37)

### Task Error Handling and Runner Integration

The `AfterRunner` class orchestrates request lifecycle closure and error boundaries using `AwaiterOnce`, `CloseController`, and a detached promise tracker. Sources: [packages/next/src/server/after/run-with-after.ts:12-33](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/run-with-after.ts#L12-L33)

```typescript
export class AfterRunner {
  private awaiter = new AwaiterOnce()
  private closeController = new CloseController()
  private finishedWithoutErrors = new DetachedPromise<void>()

  readonly context: Ctx = {
    waitUntil: this.awaiter.waitUntil.bind(this.awaiter),
    onClose: this.closeController.onClose.bind(this.closeController),
    onTaskError: (error) => this.finishedWithoutErrors.reject(error),
  }

  public async executeAfter() {
    this.closeController.dispatchClose()
    await this.awaiter.awaiting()

    this.finishedWithoutErrors.resolve()

    return this.finishedWithoutErrors.promise
  }
}
```

Sources: [packages/next/src/server/after/run-with-after.ts:12-33](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/run-with-after.ts#L12-L33)

> [!NOTE]
> When a callback or promise passed to `after()` throws or rejects, `reportTaskError` catches the error, logs it via `console.error`, and triggers `onTaskError` if defined, wrapping any handler failures in an `InvariantError`. Sources: [packages/next/src/server/after/after-context.ts:127-151](https://github.com/blade47/next.js/blob/main/packages/next/src/server/after/after-context.ts#L127-L151)

## Related

- [Server Request Lifecycle](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/server-request-lifecycle)
- [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.
