Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/shared/lib/rsc-transport.ts
- packages/next/src/server/app-render/action-handler.ts
- packages/next/src/client/components/segment-cache/decode-server-response.ts
- packages/next/src/client/components/router-reducer/fetch-server-response.ts
- packages/next/src/client/app-index.tsx
- packages/next/src/shared/lib/app-router-types.ts
- packages/next/src/server/app-render/use-flight-response.tsx
- packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts
- packages/next/src/client/components/app-router-headers.ts
- packages/next/src/server/app-render/instant-validation/instant-validation.tsx
- packages/next/src/client/flight-data-helpers.ts
- packages/next/src/server/app-render/encryption.ts
- packages/next/src/server/app-render/stream-ops.node.ts
- packages/next/src/build/templates/app-page-runtime.ts
- packages/next/src/server/app-render/collect-segment-data.tsx
- packages/next/src/server/app-render/create-component-tree.tsx
- packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts
- packages/next/src/server/app-render/dev-validation-error-delivery.ts
- packages/next/src/server/dev/serialized-errors.ts
- packages/next/src/build/webpack/loaders/next-flight-loader/index.ts
- packages/next/src/server/app-render/react-server.node.ts
- packages/next/src/server/app-render/flight-render-result.ts
- packages/next/src/client/components/segment-cache/cache.ts
Overview
RSC Transport defines the wire formats, serialization schemas, and transport tree data structures used to transmit React Server Component responses between server renderers and client routers in Next.js. It bridges server-side component tree generation with client-side ingestion, establishing the wire conventions for Segment Cache, Router Reducer, and App Rendering.
Sources: packages/next/src/shared/lib/rsc-transport.ts:1-15
Transport Protocol and Data Structures
Transport Node Representation
The transport protocol governs how React Server Component payloads are structured, serialized, and mapped between server renderers and client routers. Every transport type is prefixed with Transport to distinguish the wire format from client-local types like Segment, RouteTree, CacheNode, and FlightRouterState, ensuring client code converts out of transport types at the decode boundary and never stores them directly.
Tree payloads are represented as hierarchical node structures (FullTransportNode and PartialTransportNode) built around a shared node shape (TransportNodeShape). Full response trees account for every node in the route segment hierarchy, whereas partial trees act as overlays produced during navigations, refreshes, and server actions. Parallel routes are structured using Map instances for child slots rather than plain objects to prevent megamorphic keyed access caused by hidden class variations across app-defined slot names.
| Transport Node Field | Type | Description |
|---|---|---|
s | TransportSegment | The wire segment identity for the route node. |
d | TransportSegmentData (or optional d?) | Segment output data; always present in full nodes, optional in partial nodes. |
c | Map<string, Node> (or optional) | Parallel route child slots, stored as a Map of slot keys to transport nodes. |
h | TransportSegmentData (or optional) | Segment-level head metadata (such as title or meta tags). |
Sources: packages/next/src/shared/lib/rsc-transport.ts:136-179
Note
FullTransportNode is structurally assignable to PartialTransportNode, allowing the union type TransportNode to collapse to PartialTransportNode for the TypeScript type checker while retaining the union spelling to document developer intent.
Sources: packages/next/src/shared/lib/rsc-transport.ts:181-188
Segment Encoding and Conversion
Route segments alternate between internal representations and wire representations. A segment is encoded as either a plain string (for static segments or special keys like PAGE_SEGMENT_KEY) or an object carrying compressed property keys: n for the parameter name, t for the parameter type, k for the parameter cache key, and s for static siblings.
export type TransportSegment =
| string
| {
n: string
t: string
k: string | null
s: string
}Sources: packages/next/src/shared/lib/rsc-transport.ts:259-296
When processing fallback prerenders where dynamic parameters are absent from the initial static shell, the client patches the transport tree by parsing parameter values directly from the rendered pathname and populating missing cache keys via fillInFallbackTransportTree.
function fillInFallbackTransportTree(
node: FullTransportNode,
pathnameParts: Array<string>,
pathnamePartsIndex: number
): FullTransportNode {
const originalSegment = node.s
let newSegment: TransportSegment
let doesAppearInURL: boolean
if (typeof originalSegment === 'string') {
newSegment = originalSegment
doesAppearInURL = doesStaticSegmentAppearInURL(originalSegment)
} else {
const paramValue = parseDynamicParamFromURLPart(
originalSegment.t,
pathnameParts,
pathnamePartsIndex
)
newSegment = {
n: originalSegment.n,
t: originalSegment.t,
k: getCacheKeyForDynamicParam(paramValue),
s: originalSegment.s,
}
doesAppearInURL = true
}Sources: packages/next/src/client/flight-data-helpers.ts:77-101
Flight Streaming and Response Generation
Flight Stream Decoding and Consumer Manifests
Server-side construction of Flight data streams involves wrapping raw React Server Component outputs into specialized render results and processing streams through react-server-dom-webpack client deserializers. The getFlightStream function retrieves or initializes responses from either Node.js readable streams or Web ReadableStream instances. Client reference manifests supply module mapping metadata—selecting edgeSSRModuleMapping for the edge runtime or ssrModuleMapping for Node.js servers, alongside client module loading configurations and debug channels.
export function getFlightStream<T>(
flightStream: Readable | BinaryStreamOf<T>,
debugStream: Readable | ReadableStream<Uint8Array> | undefined,
debugEndTime: number | undefined,
nonce: string | undefined
): Promise<T>Sources: packages/next/src/server/app-render/use-flight-response.tsx:35-40
Note
When executing under client-side prerendering or validation work units in non-edge runtimes, responses are deferred to the next tick via process.nextTick before being stored in the flightResponses WeakMap.
Sources: packages/next/src/server/app-render/use-flight-response.tsx:119-135
Inlined Data Streams and Payload Instructions
To transmit hydration data and incremental payload chunks outside the core React tree render, createInlinedDataReadableStream constructs a lazy Web ReadableStream that outputs inline script tag blocks. Initial bootstrap instructions, form states, and flight data payloads are serialized into JSON arrays, escaped, and pushed into the global self.__next_f array.
| Payload Constant | Numeric Value | Description |
|---|---|---|
INLINE_FLIGHT_PAYLOAD_BOOTSTRAP | 0 | Initializes the global self.__next_f stream buffer. |
INLINE_FLIGHT_PAYLOAD_DATA | 1 | Transmits stringified Flight text chunks. |
INLINE_FLIGHT_PAYLOAD_FORM_STATE | 2 | Supplies initial form state data. |
INLINE_FLIGHT_PAYLOAD_BINARY | 3 | Transmits binary chunks encoded in base64 when UTF-8 decoding fails. |
Sources: packages/next/src/server/app-render/use-flight-response.tsx:14-18, packages/next/src/server/app-render/use-flight-response.tsx:221-237
Binary chunks that cannot be embedded directly as valid UTF-8 strings are converted to base64 strings using Buffer.from or btoa before being bundled into instruction arrays.
const base64 =
typeof Buffer !== 'undefined'
? Buffer.from(
chunk.buffer,
chunk.byteOffset,
chunk.byteLength
).toString('base64')
: btoa(String.fromCodePoint(...chunk))
htmlInlinedData = htmlEscapeJsonString(
JSON.stringify([INLINE_FLIGHT_PAYLOAD_BINARY, base64])
)
}Sources: packages/next/src/server/app-render/use-flight-response.tsx:255-266
Response Wrapping and Content Types
Completed Flight streams are wrapped into custom RenderResult subclasses to guarantee correct HTTP header assignment. FlightRenderResult forces the response content type header to match RSC_CONTENT_TYPE_HEADER, preventing downstream proxies or browsers from misinterpreting binary RSC payloads as HTML documents.
export class FlightRenderResult extends RenderResult {
constructor(
response: string | AnyStream,
metadata: RenderResultMetadata = {},
waitUntil?: Promise<unknown>
) {
super(response, {
contentType: RSC_CONTENT_TYPE_HEADER,
metadata,
waitUntil,
})
}
}Sources: packages/next/src/server/app-render/flight-render-result.ts:8-19
Client Reference and Action Manifests
Compiler Identification and Module Mapping
During the build process, the Flight client entry plugin traverses Webpack compilation chunks to identify client components and server actions, recording their module identifiers and chunk associations. The plugin creates asset manifests that map server actions and client entries for both Node.js and edge runtime targets, assigning metadata such as async status and browser or server layers.
Sources: packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1110-1137
Action Manifests and Asset Emission
Server actions are collected into runtime-specific structures (serverActions and edgeServerActions), linking action identifiers to their worker module IDs depending on whether their layer matches WEBPACK_LAYERS.actionBrowser. The resulting manifests are serialized to JSON and emitted as assets, alongside a JavaScript wrapper assigned to self.__RSC_SERVER_MANIFEST.
Sources: packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1139-1190
Note
Edge server manifests override the encryption key property with the string literal 'process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY' rather than embedding the build-time static secret directly.
Sources: packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1168-1171
Module Resource Resolution and Import Collection
Module paths are normalized using resolved resource data and query parameters to ensure client and server compilers share identical paths. For CommonJS modules, default imports automatically include the __esModule identifier alongside requested names to guarantee compatibility with React's client module proxy layer.
function getModuleResource(mod: webpack.NormalModule): string {
const modPath: string = mod.resourceResolveData?.path || ''
const modQuery = mod.resourceResolveData?.query || ''
// We have to always use the resolved request here to make sure the
// server and client are using the same module path (required by RSC), as
// the server compiler and client compiler have different resolve configs.
let modResource: string = modPath + modQuery
// Context modules don't have a resource path, we use the identifier instead.
if (mod.constructor.name === 'ContextModule') {
modResource = mod.identifier()
}
// For the barrel optimization, we need to use the match resource instead
// because there will be 2 modules for the same file (same resource path)
// but they're different modules and can't be deduped via `visitedModule`.
// The first module is a virtual re-export module created by the loader.
if (mod.matchResource?.startsWith(BARREL_OPTIMIZATION_PREFIX)) {
modResource = mod.matchResource + ':' + modResource
}
if (mod.resource === `?${WEBPACK_RESOURCE_QUERIES.metadataRoute}`) {
return getMetadataRouteResource(mod.rawRequest).filePath
}
return modResource
}Sources: packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts:1239-1265
Action Bound Argument Cryptographic Serialization
Bound Argument Encryption and Caching
Server action bound arguments are serialized through React Flight via renderToReadableStream, capturing arguments into a string payload before cryptographic operations. To guarantee reference equality for objects returned from "use cache" functions when invoked repeatedly within a single render pass, encryptActionBoundArgs is wrapped in React.cache. A cache key is constructed by combining the actionId and the serialized argument string, allowing lookup and reuse of previously encrypted arguments from resumeDataCache?.encryptedBoundArgs.
export const encryptActionBoundArgs = React.cache(
async function encryptActionBoundArgs(actionId: string, ...args: any[]) {
const workUnitStore = workUnitAsyncStorage.getStore()
const cacheSignal = workUnitStore
? getCacheSignal(workUnitStore)
: undefined
const { clientModules } = getClientReferenceManifest()Sources: packages/next/src/server/app-render/encryption.ts:108-115
const cacheKey = actionId + serialized
const cachedEncrypted = resumeDataCache?.encryptedBoundArgs.get(cacheKey)
if (cachedEncrypted) {
return cachedEncrypted
}
const encrypted = await encodeActionBoundArg(actionId, serialized)
endReadIfStarted()
if (resumeDataCache?.mutable) {
resumeDataCache.encryptedBoundArgs.set(cacheKey, encrypted)
}
return encrypted
}Sources: packages/next/src/server/app-render/encryption.ts:223-239
Note
During development mode in cache components, a dummy debug channel consisting of an empty WritableStream is passed to renderToReadableStream to strip timing information from debug output. This prevents build/render timestamps from invalidating the cache key.
Sources: packages/next/src/server/app-render/encryption.ts:164-179
Decryption and Stream Hydration WorkUnits
Decryption reverses the pipeline by fetching cached or newly decoded argument strings using decryptActionBoundArgs. The decrypted string is fed into createFromReadableStream via a custom ReadableStream controller. Depending on the active work unit store type, stream closure behavior is adjusted to handle hanging promises or immediate termination.
| Work Unit Store Type | Controller Termination Behavior | Sources |
|---|---|---|
prerender, prerender-runtime | Defers stream closure until prerendering completes or renderSignal aborts. | packages/next/src/server/app-render/encryption.ts:279-293 |
prerender-client, validation-client, prerender-legacy, request, cache, private-cache, unstable-cache, build-time-generator, undefined | Closes the controller immediately via controller.close(). | packages/next/src/server/app-render/encryption.ts:294-303 |
Sources: packages/next/src/server/app-render/encryption.ts:274-307
Client Ingestion and Stream Hydration
Browser-Side Bootstrapping and Inline Script Parsing
The client-side App Router bootstraps by evaluating app-index.tsx, which interacts with the global self.__next_f array to ingest initial Flight data pushed during server-side rendering. Chunks are dispatched into nextServerDataLoadingGlobal using a tuple-based FlightSegment schema.
Segment Opcode (seg[0]) | Payload Type | Description | Sources |
|---|---|---|---|
0 | None | Initializes initialServerDataBuffer = [] to collect subsequent chunks. | packages/next/src/client/app-index.tsx:81-83 |
1 | string | Enqueues raw text data or pushes it to the server data buffer. | packages/next/src/client/app-index.tsx:84-92 |
2 | any | Assigns form state data to initialFormStateData. | packages/next/src/client/app-index.tsx:93-94 |
3 | string (Base64) | Decodes Base64-encoded binary chunks into Uint8Array arrays. | packages/next/src/client/app-index.tsx:95-111 |
Sources: packages/next/src/client/app-index.tsx:60-112
Warning
If a chunk with opcode 1 or 3 arrives before opcode 0 has initialized initialServerDataBuffer, nextServerDataCallback throws an error stating that the bootstrap script is missing.
Sources: packages/next/src/client/app-index.tsx:84-86, packages/next/src/client/app-index.tsx:96-97
React Flight Response Consumption
Once the initialization chunks are collected, a ReadableStream is constructed to feed React via nextServerDataRegisterWriter. When DOMContentLoaded fires, any pending writers are closed to finalize hydration.
function nextServerDataRegisterWriter(ctr: ReadableStreamDefaultController) {
if (initialServerDataBuffer) {
initialServerDataBuffer.forEach((val) => {
ctr.enqueue(typeof val === 'string' ? encoder.encode(val) : val)
})
if (initialServerDataLoaded && !initialServerDataFlushed) {
// Instant Navigation Testing API: don't close or error the inline
// Flight stream. The static shell has no inline Flight data, so the
// stream is empty. Closing it would cause React to log an error about
// missing data. Leaving it open lets React treat any holes as
// "still suspended." Hydration uses the separately fetched RSC payload
// (self.__next_instant_test), not this stream.
if (isStreamErrorOrUnfinished(ctr)) {
if (!instantTestStaticFetch) {
ctr.error(
new Error(
'The connection to the page was unexpectedly closed, possibly due to the stop button being clicked, loss of Wi-Fi, or an unstable internet connection.'
)
)
}
} else {
ctr.close()
}
initialServerDataFlushed = true
initialServerDataBuffer = undefined
}
}
initialServerDataWriter = ctr
}Sources: packages/next/src/client/app-index.tsx:127-156
Note
When cache components and experimental cached navigations are enabled, the hydration stream is teed into two branches: one for React consumption and one for caching purposes.
Sources: packages/next/src/client/app-index.tsx:200-208
Segment Prefetch Payload Packaging
Tree Construction and Identity Spine Wrapping
Segment prefetch payloads are built by constructing a root-anchored transport tree starting from the terminal segment and wrapping upward along the identity spine. Each ancestor node along the spine is linked via its parallel route key, carrying its own prefetch hints if non-zero. If prefetching is disabled for an ancestor, its segment data is omitted, leaving it with identity information only so the client treats it as a non-claimable node.
let node: PartialTransportNode = { s: spine.segment }
if (spine.prefetchHints !== 0) {
node.h = spine.prefetchHints
}
if (terminal !== null) {
node.d = createStagedSegmentData(
terminal,
staleTime,
clientModules,
streamInfoStage.promise
)
}
let edgeParallelRouteKey = spine.parallelRouteKey
let ancestor = spine.parent
let bundleNode = bundle
while (ancestor !== null && edgeParallelRouteKey !== null) {
const parentNode: PartialTransportNode = {
s: ancestor.segment,
}
if (ancestor.prefetchHints !== 0) {
parentNode.h = ancestor.prefetchHints
}
if (bundleNode !== null) {
if (bundleNode.data !== null) {
parentNode.d = createStagedSegmentData(
bundleNode.data,
staleTime,
clientModules,
streamInfoStage.promise
)
}
bundleNode = bundleNode.next
}
parentNode.c = new Map([[edgeParallelRouteKey, node]])
node = parentNode
edgeParallelRouteKey = ancestor.parallelRouteKey
ancestor = ancestor.parent
}Sources: packages/next/src/server/app-render/collect-segment-data.tsx:1227-1264
Note
Root segment request keys are automatically normalized to /_index when packaging transport data, while non-root keys retain their original request identifiers.
Sources: packages/next/src/server/app-render/collect-segment-data.tsx:1275-1279
Prefetch Payload Structure and Stream Encapsulation
The assembled transport data is wrapped inside a PrefetchFlightResponse payload object alongside shell boundary offsets, runtime flags, and build identifiers before being rendered into a readable stream. Chunks emitted by the renderer are consumed asynchronously into an array while tracking the total byte length to determine the exact shell boundary offset.
| Payload Field | Type | Description | Sources |
|---|---|---|---|
t | PartialTransportData | The wrapped transport tree and optional head data. | packages/next/src/server/app-render/collect-segment-data.tsx:1288 |
a | Promise<number | null> | Resolves to the byte length after the shell stage or null if the shell is a full response. | packages/next/src/server/app-render/collect-segment-data.tsx:1289 |
u | Promise<boolean> | Indicates whether a runtime request is needed. | packages/next/src/server/app-render/collect-segment-data.tsx:1290 |
b | string | Optional build identifier. | packages/next/src/server/app-render/collect-segment-data.tsx:1292-1294 |
r | VaryParamsIterable | Optional root vary parameters. | packages/next/src/server/app-render/collect-segment-data.tsx:1295-1297 |
f | boolean | Flag set when the payload represents an upgradeable ISR fallback. | packages/next/src/server/app-render/collect-segment-data.tsx:1298-1300 |
Sources: packages/next/src/server/app-render/collect-segment-data.tsx:1287-1301
Warning
If the abort signal fires during stream consumption, error rows emitted by the abort action are explicitly discarded to match prerender halting behavior.
Sources: packages/next/src/server/app-render/collect-segment-data.tsx:1333-1338
Development Validation Error Transmission
Flight-Encoded Error Serialization
When development validation errors occur during component rendering or validation worker execution, they are serialized into Flight protocol bytes using serializeValidationErrorsToFlight. This function retrieves client modules via getClientReferenceManifest and invokes renderToNodeFlightStream passing the component module, an errors object wrapper, client modules, and stack frame filters active outside production.
export async function serializeValidationErrorsToFlight(
componentMod: AppPageModule,
errors: Error[]
): Promise<Uint8Array[]> {
const { clientModules } = getClientReferenceManifest()
const stream = renderToNodeFlightStream(
componentMod,
{ errors },
clientModules,
{ filterStackFrame }
)
const chunks: Uint8Array[] = []
for await (const chunk of stream) {
chunks.push(chunk)
}
return chunksSources: packages/next/src/server/app-render/dev-validation-error-delivery.ts:18-35
Warning
Serialization must execute in the runtime that originally generated the errors—such as an in-process render or a validation worker—to ensure matching client-reference manifests capture live Error instances complete with stacks and digests.
Sources: packages/next/src/server/app-render/dev-validation-error-delivery.ts:11-17
HMR Delivery and Stream Management
Serialized error streams are tracked in memory using errorsRscStreamsByHtmlRequestId as a mapping from HTML request identifiers to active stream instances. The Hot Module Replacement (HMR) layer interacts with this registry through dedicated lifecycle functions to transmit error payloads to the browser overlay.
| Function Name | Parameters | Description | Sources |
|---|---|---|---|
sendSerializedErrorsToClient | errorsRscStream: AnyStream, sendToClient: (message: HmrMessageSentToBrowser) => void | Converts the RSC stream to a Uint8Array and dispatches an ERRORS_TO_SHOW_IN_BROWSER HMR message. | packages/next/src/server/dev/serialized-errors.ts:10-25 |
sendSerializedErrorsToClientForHtmlRequest | htmlRequestId: string, sendToClient: (message: HmrMessageSentToBrowser) => void | Looks up and deletes the error stream for a request ID, then dispatches it. | packages/next/src/server/dev/serialized-errors.ts:27-40 |
setErrorsRscStreamForHtmlRequest | htmlRequestId: string, errorsRscStream: AnyStream | Stores an error stream against an HTML request ID. | packages/next/src/server/dev/serialized-errors.ts:42-49 |
deleteErrorsRscStreamForHtmlRequest | htmlRequestId: string | Deletes the error stream entry for a given request ID. | packages/next/src/server/dev/serialized-errors.ts:51-53 |
Sources: packages/next/src/server/dev/serialized-errors.ts:10-53
Related
On This Page
OverviewTransport Protocol and Data StructuresTransport Node RepresentationSegment Encoding and ConversionFlight Streaming and Response GenerationFlight Stream Decoding and Consumer ManifestsInlined Data Streams and Payload InstructionsResponse Wrapping and Content TypesClient Reference and Action ManifestsCompiler Identification and Module MappingAction Manifests and Asset EmissionModule Resource Resolution and Import CollectionAction Bound Argument Cryptographic SerializationBound Argument Encryption and CachingDecryption and Stream Hydration WorkUnitsClient Ingestion and Stream HydrationBrowser-Side Bootstrapping and Inline Script ParsingReact Flight Response ConsumptionSegment Prefetch Payload PackagingTree Construction and Identity Spine WrappingPrefetch Payload Structure and Stream EncapsulationDevelopment Validation Error TransmissionFlight-Encoded Error SerializationHMR Delivery and Stream ManagementRelated