Skip to main content
doc0blade47/next.jsdocumentation home
Sign in

RSC Transport

Last updated Oct 12, 2026100% grounded
Relevant source files

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

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 FieldTypeDescription
sTransportSegmentThe wire segment identity for the route node.
dTransportSegmentData (or optional d?)Segment output data; always present in full nodes, optional in partial nodes.
cMap<string, Node> (or optional)Parallel route child slots, stored as a Map of slot keys to transport nodes.
hTransportSegmentData (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 ConstantNumeric ValueDescription
INLINE_FLIGHT_PAYLOAD_BOOTSTRAP0Initializes the global self.__next_f stream buffer.
INLINE_FLIGHT_PAYLOAD_DATA1Transmits stringified Flight text chunks.
INLINE_FLIGHT_PAYLOAD_FORM_STATE2Supplies initial form state data.
INLINE_FLIGHT_PAYLOAD_BINARY3Transmits 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 TypeController Termination BehaviorSources
prerender, prerender-runtimeDefers 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, undefinedCloses 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 TypeDescriptionSources
0NoneInitializes initialServerDataBuffer = [] to collect subsequent chunks.packages/next/src/client/app-index.tsx:81-83
1stringEnqueues raw text data or pushes it to the server data buffer.packages/next/src/client/app-index.tsx:84-92
2anyAssigns form state data to initialFormStateData.packages/next/src/client/app-index.tsx:93-94
3string (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 FieldTypeDescriptionSources
tPartialTransportDataThe wrapped transport tree and optional head data.packages/next/src/server/app-render/collect-segment-data.tsx:1288
aPromise<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
uPromise<boolean>Indicates whether a runtime request is needed.packages/next/src/server/app-render/collect-segment-data.tsx:1290
bstringOptional build identifier.packages/next/src/server/app-render/collect-segment-data.tsx:1292-1294
rVaryParamsIterableOptional root vary parameters.packages/next/src/server/app-render/collect-segment-data.tsx:1295-1297
fbooleanFlag 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 chunks

Sources: 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 NameParametersDescriptionSources
sendSerializedErrorsToClienterrorsRscStream: AnyStream, sendToClient: (message: HmrMessageSentToBrowser) => voidConverts 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
sendSerializedErrorsToClientForHtmlRequesthtmlRequestId: string, sendToClient: (message: HmrMessageSentToBrowser) => voidLooks up and deletes the error stream for a request ID, then dispatches it.packages/next/src/server/dev/serialized-errors.ts:27-40
setErrorsRscStreamForHtmlRequesthtmlRequestId: string, errorsRscStream: AnyStreamStores an error stream against an HTML request ID.packages/next/src/server/dev/serialized-errors.ts:42-49
deleteErrorsRscStreamForHtmlRequesthtmlRequestId: stringDeletes 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

Was this page helpful?