Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/server/app-render/after-task-async-storage-instance.ts
- packages/next/src/server/app-render/action-async-storage-instance.ts
- packages/next/src/server/async-storage/request-store.ts
- packages/next/src/server/app-render/work-unit-async-storage.external.ts
- packages/next/src/server/app-render/console-async-storage-instance.ts
- packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts
- packages/next/src/server/app-render/work-async-storage.external.ts
- packages/next/src/server/app-render/async-local-storage.ts
- packages/next/src/server/request/cookies.ts
- packages/next/src/experimental/testmode/context.ts
- packages/next/src/server/app-render/work-async-storage-instance.ts
- packages/next/src/server/web/adapter.ts
- packages/next/src/server/base-server.ts
- packages/next/src/server/app-render/work-unit-async-storage-instance.ts
- packages/next/src/server/async-storage/draft-mode-provider.ts
- packages/next/src/server/request/headers.ts
- packages/next/src/server/next-server.ts
- packages/next/src/client/components/segment-cache/navigation-testing-lock.ts
- packages/next/src/server/lib/trace/request-insights-identity.ts
- packages/next/src/server/request/draft-mode.ts
- packages/next/src/server/app-render/action-async-storage.external.ts
- packages/next/src/server/async-storage/with-store.ts
- packages/next/src/server/after/builtin-request-context.ts
- packages/next/src/server/app-render/after-task-async-storage.external.ts
- packages/next/src/server/app-render/console-async-storage.external.ts
- packages/next/src/client/components/server-async-storage.ts
- packages/next/src/server/app-render/dynamic-access-async-storage.external.ts
- packages/next/src/server/web/spec-extension/adapters/request-cookies.ts
- packages/next/src/server/async-storage/work-store.ts
- packages/next/src/client/components/server-async-storage.browser.ts
Overview
Next.js uses asynchronous storage infrastructure built on AsyncLocalStorage to propagate request-scoped execution contexts, render metadata, and dynamic states across asynchronous boundaries. By maintaining realm-safe singletons via global symbols and fallback shims, the system tracks active rendering phases, request headers, cookies, and draft mode parameters across Node.js servers, worker threads, and the Edge Runtime. These storage contexts coordinate request lifecycles throughout Server Architecture, managing dynamic data access enforcement and execution boundaries for App Rendering and Route Handlers.
Sources: packages/next/src/server/app-render/async-local-storage.ts:36-83, packages/next/src/server/app-render/work-unit-async-storage.external.ts:28-34, packages/next/src/server/request/cookies.ts:36-147
Global Storage Infrastructure
Global AsyncLocalStorage Singletons
Next.js initializes and exports several core AsyncLocalStorage singletons to manage request contexts, action execution, console logging interception, dynamic access tracking, and background tasks. Each storage instance is created by calling getOrCreateGlobalAsyncLocalStorage with a unique identifier string combined with the current Next.js version (process.env.__NEXT_VERSION) via Symbol.for.
export function getOrCreateGlobalAsyncLocalStorage<Store extends {}>(
name: string
): AsyncLocalStorage<Store> {
const key = Symbol.for(`@next/${name}@${process.env.__NEXT_VERSION}`)
const globalStore = globalThis as typeof globalThis & {
[key: symbol]: AsyncLocalStorage<Store> | undefined
}
return (globalStore[key] ??= createAsyncLocalStorage<Store>())
}Sources: packages/next/src/server/app-render/async-local-storage.ts:73-83
| Singleton Instance Name | Storage Identifier String | Associated Type |
|---|---|---|
actionAsyncStorageInstance | 'action-async-storage' | ActionAsyncStorage |
afterTaskAsyncStorageInstance | 'after-task-async-storage' | AfterTaskAsyncStorage |
consoleAsyncStorageInstance | 'console-async-storage' | ConsoleAsyncStorage |
dynamicAccessAsyncStorageInstance | 'dynamic-access-async-storage' | DynamicAccessStorage |
workAsyncStorageInstance | 'work-async-storage' | WorkAsyncStorage |
workUnitAsyncStorageInstance | 'work-unit-async-storage' | WorkUnitAsyncStorage |
Sources: packages/next/src/server/app-render/after-task-async-storage-instance.ts:4-5, packages/next/src/server/app-render/action-async-storage-instance.ts:4-5, packages/next/src/server/app-render/console-async-storage-instance.ts:4-5, packages/next/src/server/app-render/dynamic-access-async-storage-instance.ts:4-5, packages/next/src/server/app-render/work-async-storage-instance.ts:4-5, packages/next/src/server/app-render/work-unit-async-storage-instance.ts:4-5
Note
Using Symbol.for with the Next.js version ensures that if multiple distinct versions of Next.js are evaluated within the same JavaScript realm, their async storage instances remain isolated, preventing incompatible store shape collisions.
Sources: packages/next/src/server/app-render/async-local-storage.ts:69-72
Fallback Shims and Environment Safety
When AsyncLocalStorage is unavailable in the execution environment, createAsyncLocalStorage falls back to FakeAsyncLocalStorage. The fake shim implements the AsyncLocalStorage interface by returning undefined for getStore() and throwing an invariant error for all lifecycle operations (disable, run, exit, enterWith).
class FakeAsyncLocalStorage<Store extends {}>
implements AsyncLocalStorage<Store>
{
disable(): void {
throw sharedAsyncLocalStorageNotAvailableError
}
getStore(): Store | undefined {
// This fake implementation of AsyncLocalStorage always returns `undefined`.
return undefined
}
run<R>(): R {
throw sharedAsyncLocalStorageNotAvailableError
}
exit<R>(): R {
throw sharedAsyncLocalStorageNotAvailableError
}
enterWith(): void {
throw sharedAsyncLocalStorageNotAvailableError
}
static bind<T>(fn: T): T {
return fn
}
}Sources: packages/next/src/server/app-render/async-local-storage.ts:7-34
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:39-46
Warning
Module loader symlink resolution bugs (such as those tied to fs.realpathSync in next dev under package managers like pnpm) can cause the same file to be evaluated multiple times in a single realm, creating duplicate module instances. Storing singletons on globalThis via symbol keys prevents context loss across these duplicate module evaluations.
Sources: packages/next/src/server/app-render/async-local-storage.ts:50-68
For client-side compilation, server-async-storage.ts is aliased to server-async-storage.browser.ts, exporting undefined stubs for actionAsyncStorage, workAsyncStorage, and workUnitAsyncStorage to prevent server-only modules from leaking into browser bundles.
// Browser variant of `./server-async-storage`. The server-only async-storage
// singletons don't exist in the browser, so these are `undefined`. Consumers
// guard usage behind `typeof window === 'undefined'`, so the stubs are never
// dereferenced.
export const actionAsyncStorage = undefined
export const workAsyncStorage = undefined
export const workUnitAsyncStorage = undefinedSources: packages/next/src/client/components/server-async-storage.browser.ts:1-7, packages/next/src/client/components/server-async-storage.ts:1-9
Work Store Lifecycle
Store Creation and Prerender Configuration
The WorkStore is instantiated through factory functions that construct and initialize store instances based on rendering contexts and build options. The createWorkStore and createPrerenderWorkStore functions invoke createWorkStoreImpl, passing parameters that dictate fetch metrics tracking behavior.
| Factory Function | Metric Tracking Flag Condition |
|---|---|
createWorkStore | !!process.env.__NEXT_DEV_SERVER |
createPrerenderWorkStore | !!process.env.__NEXT_DEV_SERVER || !!process.env.NEXT_DEBUG_BUILD || process.env.NEXT_SSG_FETCH_METRICS === '1' |
Sources: packages/next/src/server/async-storage/work-store.ts:91-102
Initialization and Context Capture
During initialization, createWorkStoreImpl maps properties from WorkStoreContext and RenderOpts directly onto the WorkStore object. It normalizes the route using normalizeAppPath(page), resolves the incremental cache by falling back to (globalThis as any).__incrementalCache when edge runtime local environments lack an explicit instance, and records the request start time using performance.timeOrigin + performance.now().
const store: WorkStore = {
page,
route: normalizeAppPath(page),
incrementalCache:
// we fallback to a global incremental cache for edge-runtime locally
// so that it can access the fs cache without mocks
renderOpts.incrementalCache || (globalThis as any).__incrementalCache,
cacheLifeProfiles: renderOpts.cacheLifeProfiles,
useCacheTimeout: renderOpts.experimental.useCacheTimeout,
durableUseCacheEntries: renderOpts.experimental.durableUseCacheEntries,
staticPageGenerationTimeout: renderOpts.staticPageGenerationTimeout,
isBuildTimePrerendering: renderOpts.isBuildTimePrerendering,
fetchCache: renderOpts.fetchCache,
isOnDemandRevalidate: renderOpts.isOnDemandRevalidate,
requestId: undefined,
htmlRequestId: undefined,
isDraftMode: renderOpts.isDraftMode,
isPrefetchRequest,
buildId,
deploymentId,
reactLoadableManifest: renderOpts?.reactLoadableManifest || {},
assetPrefix: renderOpts?.assetPrefix || '',
nonce,
afterContext: createAfterContext(renderOpts),
cacheComponentsEnabled: renderOpts.cacheComponents,
validationLevel: renderOpts.validationLevel,
previouslyRevalidatedTags,
requestStartTime: performance.timeOrigin + performance.now(),
refreshTagsByCacheKind: createRefreshTagsByCacheKind(),
runInCleanSnapshot: createSnapshot(),
shouldTrackFetchMetrics,
clientComponentLoadTracker: undefined,
reactServerErrorsByDigest: new Map(),
}Sources: packages/next/src/server/async-storage/work-store.ts:116-152
Note
renderOpts.store is temporarily assigned the newly created store reference to support legacy code paths accessing store values outside the active request execution context.
Sources: packages/next/src/server/async-storage/work-store.ts:154-156
After-Context and Lazy Tag Refreshing
The lifecycle builder constructs an AfterContext instance using lifecycle options such as waitUntil, onClose, and onTaskError. Simultaneously, it initializes the refreshTagsByCacheKind map by iterating over available cache handler entries and wrapping handlers that expose a refreshTags method into LazyResult instances.
function createAfterContext(renderOpts: RequestLifecycleOpts): AfterContext {
const { waitUntil, onClose, onAfterTaskError } = renderOpts
return new AfterContext({
waitUntil,
onClose,
onTaskError: onAfterTaskError,
})
}
/**
* Creates a map with lazy results that refresh tags for the respective cache
* kind when they're awaited for the first time.
*/
function createRefreshTagsByCacheKind(): Map<string, LazyResult<void>> {
const refreshTagsByCacheKind = new Map<string, LazyResult<void>>()
const cacheHandlers = getCacheHandlerEntries()
if (cacheHandlers) {
for (const [kind, cacheHandler] of cacheHandlers) {
if ('refreshTags' in cacheHandler) {
refreshTagsByCacheKind.set(
kind,
createLazyResult(async () => cacheHandler.refreshTags())
)
}
}
}
return refreshTagsByCacheKind
}Sources: packages/next/src/server/async-storage/work-store.ts:160-189
Static Generation Snapshotting
Static generation and cache entry creation require isolation from request-specific state. The runInCleanSnapshot property on the store is initialized via createSnapshot(), supplying a clean AsyncLocalStorage snapshot wrapper that prevents cache generation routines from reading request-bound scopes like cookies() inside React.cache().
refreshTagsByCacheKind: createRefreshTagsByCacheKind(),
runInCleanSnapshot: createSnapshot(),Sources: packages/next/src/server/async-storage/work-store.ts:147-148, packages/next/src/server/app-render/work-async-storage.external.ts:197-200
Work Unit Store Hierarchy
Common Work Unit Stores and Phase Management
Work unit stores represent different execution scopes in the App Router, separating active requests, prerenders, and background tasks. Every work unit store extends CommonWorkUnitStore, which defines a mutable phase property and readonly implicitTags.
| Work Unit Phase | Description |
|---|---|
'action' | Active execution of Server Actions responding to client mutations |
'render' | Server-side rendering of Server Component trees and route segments |
'after' | Execution of deferred work via background tasks or after callbacks |
Sources: packages/next/src/server/app-render/work-unit-async-storage.external.ts:28-34
Request and Prerender Contexts
The RequestStore interface models active HTTP requests and supports mutable cookies, draft mode tracking, header access, and incremental resume data caches. It maintains asynchronous API promises and handles development-only flags like needsRuntimeShell and hasIncompatibleShellContent to distinguish static and runtime prerender behaviors.
export interface RequestStore extends CommonWorkUnitStore {
readonly type: 'request'
/**
* The URL of the request. This only specifies the pathname and the search
* part of the URL.
*/
readonly url: {
/**
* The pathname of the requested URL.
*/
readonly pathname: string
/**
* The search part of the requested URL. If the request did not provide a
* search part, this will be an empty string.
*/
readonly search: string
}
readonly headers: ReadonlyHeaders
// This is mutable because we need to reassign it when transitioning from the action phase to the render phase.
// The cookie object itself is deliberately read only and thus can't be updated.
cookies: ReadonlyRequestCookies
readonly mutableCookies: ResponseCookies
readonly userspaceMutableCookies: ResponseCookies
readonly draftMode: DraftModeProvider
readonly isHmrRefresh?: boolean
readonly serverComponentsHmrCache?: ServerComponentsHmrCache
readonly hmrRefreshHash?: string
readonly rootParams: Params
/**
* The resume data cache for this request. Either a mutable
* `PrerenderResumeDataCache` (e.g. during a dev warmup that fills caches) or
* an immutable `RenderResumeDataCache` (e.g. when resuming from a postponed
* state). Narrow via `resumeDataCache.mutable` to tell them apart.
*/
resumeDataCache: ResumeDataCache | null
Sources: packages/next/src/server/app-render/work-unit-async-storage.external.ts:36-76
Note
PrerenderStoreModern covers client, server, and runtime prerendering modes along with validation stores, utilizing conditional properties to track cache population without explicitly branching RSC and SSR code paths.
Sources: packages/next/src/server/app-render/work-unit-async-storage.external.ts:149-154
Specialized Asynchronous Storages
Action execution, background tasks, and dynamic access tracking operate through dedicated AsyncLocalStorage instances. ActionStore tracks whether execution is part of an action or an App Route handler. AfterTaskStore captures the rootTaskSpawnphase corresponding to the phase in which the topmost after call was invoked.
export interface ActionStore {
readonly isAction?: boolean
readonly isAppRoute?: boolean
}Sources: packages/next/src/server/app-render/action-async-storage.external.ts:5-8, packages/next/src/server/app-render/after-task-async-storage.external.ts:7-14
Dynamic access tracking relies on DynamicAccessAsyncStore, which holds an AbortController and a reason restricted to 'fallback-params' or 'runtime'. The abortOnDynamicAccess function updates the store reason unless it is already marked as 'runtime' and aborts the associated controller.
export interface DynamicAccessAsyncStore {
readonly abortController: AbortController
reason: DynamicAccessReason | null
}
export type DynamicAccessReason = 'fallback-params' | 'runtime'Sources: packages/next/src/server/app-render/dynamic-access-async-storage.external.ts:6-11, packages/next/src/server/app-render/dynamic-access-async-storage.external.ts:16-29
Warning
Cache evaluation routines reading both fallback and runtime parameters will upgrade the cancellation reason from fallback-only to runtime, preventing stale static optimizations when runtime data is touched.
Sources: packages/next/src/server/app-render/dynamic-access-async-storage.external.ts:21-28
Request Store Composition
Request Store Builders and Inputs
The construction of a RequestStore instance relies on specialized creator functions that initialize phase contexts, route parameters, request headers, cookies, and cache references. createRequestStoreForRender initializes a store with a 'render' phase by default, whereas createRequestStoreForAPI defaults to an 'action' phase with empty root parameters ({}) and null resume data caches. Both helper functions delegate initialization to the core createRequestStore function, which accepts a RequestStoreInputs descriptor containing raw incoming request properties and optional HMR or preview properties.
| Creator Function | Default Phase | Root Parameters | Resume Data Cache |
|---|---|---|---|
createRequestStoreForRender | 'render' | rootParams (passed) | resumeDataCache |
createRequestStoreForAPI | 'action' | {} (empty) | null |
Sources: packages/next/src/server/async-storage/request-store.ts:184-244
Note
Side-process consumers, such as the 'use cache' deadlock probe worker, invoke createRequestStore directly with a serializable snapshot of headers and metadata when a native req/res pair is absent.
Sources: packages/next/src/server/async-storage/request-store.ts:246-253
Caching and Lazy Initialization
The RequestStore object uses internal lazy getters backed by a private cache object to construct and memoize expensive adapter instances on first access. Properties including headers, cookies, mutableCookies, userspaceMutableCookies, and draftMode are instantiated on demand, preventing unnecessary parsing overhead for requests that do not touch them.
const cache: {
headers?: ReadonlyHeaders
cookies?: ReadonlyRequestCookies
mutableCookies?: ResponseCookies
userspaceMutableCookies?: ResponseCookies
draftMode?: DraftModeProvider
} = {}
return {
type: 'request',
phase,
implicitTags,
// Rather than just using the whole `url` here, we pull the parts we want
// to ensure we don't use parts of the URL that we shouldn't. This also
// lets us avoid requiring an empty string for `search` in the type.
url: { pathname: url.pathname, search: url.search ?? '' },
rootParams,
get headers() {
if (!cache.headers) {
// Seal the headers object that'll freeze out any methods that could
// mutate the underlying data.
cache.headers = getHeaders(headers)
}
return cache.headers
},
get cookies() {
if (!cache.cookies) {
// if middleware is setting cookie(s), then include those in
// the initial cached cookies so they can be read in render
const requestCookies = new RequestCookies(HeadersAdapter.from(headers))
mergeMiddlewareCookies(headers, requestCookies)
// Seal the cookies object that'll freeze out any methods that could
// mutate the underlying data.
cache.cookies = RequestCookiesAdapter.seal(requestCookies)
}
return cache.cookies
},
set cookies(value: ReadonlyRequestCookies) {
cache.cookies = value
},
get mutableCookies() {
if (!cache.mutableCookies) {
const mutableCookies = getMutableCookies(headers, onUpdateCookies)
mergeMiddlewareCookies(headers, mutableCookies)
cache.mutableCookies = mutableCookies
}
return cache.mutableCookies
},
get userspaceMutableCookies() {
if (!cache.userspaceMutableCookies) {
const userspaceMutableCookies =
createCookiesWithMutableAccessCheck(this)
cache.userspaceMutableCookies = userspaceMutableCookies
}
return cache.userspaceMutableCookies
},
get draftMode() {
if (!cache.draftMode) {
cache.draftMode = new DraftModeProvider(
previewProps,
headers,
this.cookies,
this.mutableCookies
)
}
return cache.draftMode
},Sources: packages/next/src/server/async-storage/request-store.ts:269-342
Cookie initialization parses request cookies from headers and automatically merges any middleware-set cookies present in the 'x-middleware-set-cookie' header via mergeMiddlewareCookies. The sealed cookies property returns a read-only adapter, while mutableCookies provides a ResponseCookies instance capable of tracking modifications and triggering onUpdateCookies callbacks.
get cookies() {
if (!cache.cookies) {
// if middleware is setting cookie(s), then include those in
// the initial cached cookies so they can be read in render
const requestCookies = new RequestCookies(HeadersAdapter.from(headers))
mergeMiddlewareCookies(headers, requestCookies)
// Seal the cookies object that'll freeze out any methods that could
// mutate the underlying data.
cache.cookies = RequestCookiesAdapter.seal(requestCookies)
}
return cache.cookies
},
set cookies(value: ReadonlyRequestCookies) {
cache.cookies = value
},
get mutableCookies() {
if (!cache.mutableCookies) {
const mutableCookies = getMutableCookies(headers, onUpdateCookies)
mergeMiddlewareCookies(headers, mutableCookies)
cache.mutableCookies = mutableCookies
}
return cache.mutableCookies
},Sources: packages/next/src/server/async-storage/request-store.ts:295-322
Draft Mode and Phase Synchronization
Draft mode evaluation is handled by the DraftModeProvider class, which inspects incoming headers for on-demand revalidation flags and validates the prerender bypass cookie against the configured previewModeId. In non-production environments, previewModeId accepts a 'development-id' fallback match regardless of the exact cookie hash value.
| Method / Getter | Action Description |
|---|---|
isEnabled | Evaluates whether the prerender bypass cookie matches the preview ID and on-demand revalidation is absent |
enable() | Sets the prerender bypass cookie (__prerender_bypass) into mutable cookies with httpOnly: true and path / |
disable() | Expires the prerender bypass cookie immediately by setting expires: new Date(0) |
Sources: packages/next/src/server/async-storage/draft-mode-provider.ts:11-94
Caution
Calling enable() when previewModeId is undefined throws an immediate invariant error because the bypass cookie value cannot be determined.
Sources: packages/next/src/server/async-storage/draft-mode-provider.ts:59-65
When request phases transition or mutable cookies are updated, synchronizeMutableCookies converts updated response cookies back into sealed request cookies so subsequent reads reflect the mutations.
export function synchronizeMutableCookies(store: RequestStore) {
// TODO: does this need to update headers as well?
store.cookies = RequestCookiesAdapter.seal(
responseCookiesToRequestCookies(store.mutableCookies)
)
}Sources: packages/next/src/server/async-storage/request-store.ts:354-359
Request Data Access Enforcement
Dynamic Resolution and Bailout Guards
When application code invokes headers(), cookies(), or draftMode(), Next.js resolves the request data context via workAsyncStorage and workUnitAsyncStorage. If either store is missing, execution immediately invokes throwForMissingRequestStore. When a work store is present, calls enforce structural constraints, starting with phase validation: if a workUnitStore is active and isRequestApiAllowedInCurrentPhase evaluates to false, accessing cookies() or headers() inside an after() callback throws an unsupported usage error. If workStore.forceStatic is enabled, all dynamic enforcement logic is bypassed, returning an empty, untracked cookies or headers object. If workStore.dynamicShouldError is true, a StaticGenBailoutError is thrown, aborting static generation.
Sources: packages/next/src/server/request/cookies.ts:36-60, packages/next/src/server/request/headers.ts:43-94
Warning
Accessing cookies() or headers() inside a "use cache" block or an unstable_cache invocation throws specialized cache error messages (createCookiesInUseCacheError, createHeadersInUseCacheError, or their unstable counterparts) to prevent non-deterministic dynamic reads inside cached boundaries.
Sources: packages/next/src/server/request/cookies.ts:61-70, packages/next/src/server/request/headers.ts:62-72
Work Unit Store Resolution Branches
Depending on the specific workUnitStore.type, request data access follows distinct execution branches that determine whether data is tracked, delayed, or rejected.
| Work Unit Store Type | cookies() Behavior | headers() Behavior |
|---|---|---|
'cache' / 'unstable-cache' | Throws use-cache or unstable-cache error | Throws use-cache or unstable-cache error |
'build-time-generator' | Throws wrong-context build time error | Throws wrong-context build time error |
'prerender' | Returns hanging session data promise | Returns hanging session data promise |
'prerender-client' / 'validation-client' | Throws InvariantError (Client Component usage) | Throws InvariantError (Client Component usage) |
'prerender-legacy' | Interrupts static generation (throwToInterruptStaticGeneration) | Interrupts static generation (throwToInterruptStaticGeneration) |
'prerender-runtime' | Delays via stagedRendering or returns untracked | Delays via stagedRendering or returns untracked |
'private-cache' | Returns untracked workUnitStore.cookies | Returns untracked workUnitStore.headers |
'request' | Tracks dynamic data and returns mutable or readonly cookies | Tracks dynamic data and returns headers |
Sources: packages/next/src/server/request/cookies.ts:61-142, packages/next/src/server/request/headers.ts:62-153
Draft Mode Access and Mutation Tracking
The draftMode() function returns a promise resolving to a DraftMode instance. Accessing .isEnabled or invoking .enable() and .disable() triggers specific enforcement paths. In development environments without a prefetch request, draftMode() returns a proxied promise instrumented with createDraftModeWithDevWarnings that logs errors if properties are accessed synchronously without unwrapping the promise via await or React.use().
function createDraftModeWithDevWarnings(
underlyingProvider: null | DraftModeProvider,
route: undefined | string
): Promise<DraftMode> {
const instance = new DraftMode(underlyingProvider)
const promise = Promise.resolve(instance)
const proxiedPromise = new Proxy(promise, {
get(target, prop, receiver) {
switch (prop) {
case 'isEnabled':
warnForSyncAccess(route, `\`draftMode().${prop}\``)
break
case 'enable':
case 'disable': {
warnForSyncAccess(route, `\`draftMode().${prop}()\``)
break
}
default: {
// We only warn for well-defined properties of the draftMode object.
}
}
return ReflectAdapter.get(target, prop, receiver)
},
})
return proxiedPromise
}Sources: packages/next/src/server/request/draft-mode.ts:118-146
When .enable() or .disable() is called, trackDynamicDraftMode verifies that the current work unit phase is not 'after', throws a StaticGenBailoutError if dynamicShouldError is enabled, and handles cache and prerender boundaries:
'cache'and'private-cache'throwcreateDraftModeMutationInUseCacheError.'unstable-cache'throwscreateDraftModeMutationInUnstableCacheError.'prerender'and'prerender-runtime'trigger an abort and throw synchronous request access errors requiringawait connection().'prerender-legacy'setsworkUnitStore.revalidate = 0and throws aDynamicServerError.
Sources: packages/next/src/server/request/draft-mode.ts:194-271
Context Propagation Across Tasks
Test Modes and Request Context Propagation
Next.js propagates test parameters, proxy routing ports, and test data across asynchronous task boundaries using specialized AsyncLocalStorage instances. The test mode infrastructure defines TestReqInfo objects containing a request URL, proxyPort number, and testData string, extracted from incoming requests via TestRequestReader implementations.
export interface TestReqInfo {
url: string
proxyPort: number
testData: string
}Sources: packages/next/src/experimental/testmode/context.ts:3-7
The withRequest helper checks for the presence of the next-test-proxy-port header on incoming requests. If found, it runs the provided asynchronous or synchronous callback within the testStorage AsyncLocalStorage context; otherwise, it executes the callback directly.
function extractTestInfoFromRequest<R>(
req: R,
reader: TestRequestReader<R>
): TestReqInfo | undefined {
const proxyPortHeader = reader.header(req, 'next-test-proxy-port')
if (!proxyPortHeader) {
return undefined
}
const url = reader.url(req)
const proxyPort = Number(proxyPortHeader)
const testData = reader.header(req, 'next-test-data') || ''
return { url, proxyPort, testData }
}
export function withRequest<R, T>(
req: R,
reader: TestRequestReader<R>,
fn: () => T
): T {
const testReqInfo = extractTestInfoFromRequest(req, reader)
if (!testReqInfo) {
return fn()
}
return testStorage.run(testReqInfo, fn)
}Sources: packages/next/src/experimental/testmode/context.ts:16-40
To retrieve the current test context, getTestReqInfo checks the active testStorage store first, falling back to an explicit request extraction if optional parameters are provided.
export function getTestReqInfo<R>(
req?: R,
reader?: TestRequestReader<R>
): TestReqInfo | undefined {
const testReqInfo = testStorage.getStore()
if (testReqInfo) {
return testReqInfo
}
if (req && reader) {
return extractTestInfoFromRequest(req, reader)
}
return undefined
}Sources: packages/next/src/experimental/testmode/context.ts:42-54
Tracing Spans and Platform Contexts
Request tracing spans and platform-provided request contexts bridge low-level server initialization and runtime execution via dedicated storage layers. Request insights identity tracking (RequestInsightsIdentity) covers the portion of BaseServer request handling that executes before App Render initializes work storage (workAsyncStorage).
export type RequestInsightsIdentity = {
requestId: string
kind?: RequestInsightKind
htmlRequestId: string
url: string | undefined
}Sources: packages/next/src/server/lib/trace/request-insights-identity.ts:5-10
This metadata is managed through a global AsyncLocalStorage store instance initialized via request-insights-identity-storage.
// This storage covers the part of BaseServer request handling that runs before
// App Render creates workAsyncStorage. Once available, workStore remains the
// primary identity source for locally recorded spans.
function getRequestInsightsIdentityStorage(): AsyncLocalStorage<RequestInsightsIdentity> {
return getOrCreateGlobalAsyncLocalStorage('request-insights-identity-storage')
}
export function runWithRequestInsightsIdentity<T>(
identity: RequestInsightsIdentity,
fn: () => T
): T {
return getRequestInsightsIdentityStorage().run(identity, fn)
}
export function getRequestInsightsIdentity():
| RequestInsightsIdentity
| undefined {
return getRequestInsightsIdentityStorage().getStore()
}Sources: packages/next/src/server/lib/trace/request-insights-identity.ts:12-30
Similarly, platform-provided request contexts (such as those containing waitUntil promises for deferred execution) are stored on globalThis under the symbol @next/request-context. getBuiltinRequestContext resolves this symbol from global state, while createLocalRequestContext wraps an AsyncLocalStorage instance to expose a .get() and .run() interface matching platform requirements.
export function getBuiltinRequestContext():
| BuiltinRequestContextValue
| undefined {
const _globalThis = globalThis as GlobalThisWithRequestContext
const ctx = _globalThis[NEXT_REQUEST_CONTEXT_SYMBOL]
return ctx?.get()
}
const NEXT_REQUEST_CONTEXT_SYMBOL = Symbol.for('@next/request-context')
type GlobalThisWithRequestContext = typeof globalThis & {
[NEXT_REQUEST_CONTEXT_SYMBOL]?: BuiltinRequestContext
}
/** A request context provided by the platform. */
export type BuiltinRequestContext = {
get(): BuiltinRequestContextValue | undefined
}
export type RunnableBuiltinRequestContext = BuiltinRequestContext & {
run<T>(value: BuiltinRequestContextValue, callback: () => T): T
}
export type BuiltinRequestContextValue = {
waitUntil?: WaitUntil
}
export type WaitUntil = (promise: Promise<any>) => void
/** "@next/request-context" has a different signature from AsyncLocalStorage,
* matching [AsyncContext.Variable](https://github.com/tc39/proposal-async-context).
* We don't need a full AsyncContext adapter here, just having `.get()` is enough
*/
export function createLocalRequestContext(): RunnableBuiltinRequestContext {
const storage = createAsyncLocalStorage<BuiltinRequestContextValue>()
return {
get: () => storage.getStore(),
run: (value, callback) => storage.run(value, callback),
}
}Sources: packages/next/src/server/after/builtin-request-context.ts:3-41
Related
On This Page
OverviewGlobal Storage InfrastructureGlobal AsyncLocalStorage SingletonsFallback Shims and Environment SafetyWork Store LifecycleStore Creation and Prerender ConfigurationInitialization and Context CaptureAfter-Context and Lazy Tag RefreshingStatic Generation SnapshottingWork Unit Store HierarchyCommon Work Unit Stores and Phase ManagementRequest and Prerender ContextsSpecialized Asynchronous StoragesRequest Store CompositionRequest Store Builders and InputsCaching and Lazy InitializationDraft Mode and Phase SynchronizationRequest Data Access EnforcementDynamic Resolution and Bailout GuardsWork Unit Store Resolution BranchesDraft Mode Access and Mutation TrackingContext Propagation Across TasksTest Modes and Request Context PropagationTracing Spans and Platform ContextsRelated