<Counter/>→ proxy → manifest → [id,chunks,name] →__webpack_require__
the client-reference bridge · source → hydration · first principles
contents

00 Explorable explanation · the missing piece, made mechanical

One <Counter/> becomes a registered reference, a manifest row, a Flight tuple, a required module — and only then a live, clickable button.

"React records a reference" is not the explanation — it is the part that still needs explaining. The real bridge is a chain of concrete artifacts: a "use client" source file becomes a build-time client reference proxy, recorded in a client reference manifest; during the RSC render React Flight resolves that reference into module metadata — a module id, chunk filenames, and an export name; the metadata rides the RSC payload to the browser, where the Flight client decoder preloads the chunk, calls __webpack_require__ by id, reads the export, and hydrates the DOM the browser already painted. Press play and ride the whole line, station by station.

▶ the transit line · build time → request/server → browser · 10 stations
00 / 00
FRAME 00 · —

Press play to ride the line from source file to hydration

Cyan stations happen at build time. Amber stations happen on the server during the request. Violet stations happen in the browser. Watch the same component change form at every stop.

build time — proxy & manifest are written request / server — RSC render & Flight browser — decode, require, hydrate a common misconception
the correction that unlocks everything A client reference does not mean "the browser guesses which component to use." It means the server serialized enough bundler metadata for the browser runtime to load the exact JS chunk, call __webpack_require__ with the exact module id, and read the exact export. The bridge is plumbing, not magic.
3
identities of one component
2
server-side passes
1
manifest lookup per ref
0 KB
server-component code shipped

01 Two related server-side products

one render · two outputs

The server does two things, not one

App Router rendering splits into two related server-side products from a single render. React renders Server Components into the RSC / Flight payload; then Next uses that payload plus Client Component JavaScript instructions to render HTML. On the client, the HTML is the immediate preview, the RSC payload reconciles the server and client trees, and JavaScript hydrates the Client Components. Press play to watch one render fan out into both.

▶ Page() runs once → ( RSC / Flight payload ) + ( HTML ) → one response
00 / 00
FRAME 00 · —

Press play to split one render into two products

The RSC pass answers "what is the tree, where are the client boundaries, what props cross?" The HTML pass answers "what can the browser paint right now?"

the RSC pass asks

What is the component tree result? Where are the Client Component boundaries? What props cross from server to client? Which client modules must the browser know about?

the HTML pass asks

What can the browser paint immediately? It uses the RSC payload plus Client Component JS instructions to produce first-frame markup — even for the client boundaries.

So a Client Component wears a different hat in each phase. During the RSC render it is a reference / boundary. During the HTML render it may be prerendered for initial markup. During hydration it becomes a real browser module loaded from JS chunks. Same component, three jobs — which is exactly the next idea.

02 The correct mental model

module graph first · render tree second

"use client" marks a module boundary, not a visual subtree

The phrase "use client" does not first mark a region of the screen. It introduces a server–client boundary in the module dependency tree and creates a subtree of client modules. During render the framework server-renders the root and walks the render tree, opting out of evaluating any code imported from client-marked code. Press play to peel the one render tree apart into two module graphs.

▶ one render tree → split along "use client" → server graph + client graph
00 / 00
FRAME 00 · —

Press play to separate graph from tree

The render tree stays one logical UI. The module graph splits in two. The server never calls Counter() as ordinary code — that boundary becomes a client reference proxy.

server graph
// stays on the server
app/page.tsx
  ├─ db queries
  ├─ server-only utilities
  ├─ layout.tsx
  └─ non-"use client" components
client graph
// crosses to the browser
app/Counter.tsx   "use client"
  ├─ react useState
  ├─ browser event handlers
  └─ modules imported by Counter.tsx
the render tree is still one UI The split is in the module graph, but the rendered interface is unified: <Page> contains <main> contains <h1>Dashboard</h1> and <Counter/>. The server walks that tree, renders the server parts, and at the <Counter/> boundary it does not evaluate the client module — it records a reference instead.

03 The signature idea

one symbol · three identities

Counter has three identities at once

This is the whole bridge in one frame. The symbol you wrote — <Counter/> — exists as three different things, and the manifest plus Flight metadata are the threads that connect them. Press play to watch each identity appear.

▶ source module · server-side reference/proxy · browser module
00 / 00
FRAME 00 · —

Press play to reveal the three identities

The source module is the file you wrote. The server-side reference is a registered proxy that only knows "a client component lives here." The browser module is the real JS chunk that runs.

identity 1compile time
Source module
app/Counter.tsx — the actual implementation you authored, marked "use client".
identity 2server render
Server reference / proxy
A registered ClientReference: "this client component exists at this resource key + export." It throws if you call it on the server.
identity 3browser
Browser module
The real JS chunk holding the Counter function, loaded on demand and hydrated into the painted DOM.

04 The simple case, made precise

no client boundary · output ships, code does not

A Server Component can produce HTML without ever shipping JS

"Server Components don't run in the browser" does not mean "Server Components don't produce HTML." It means the browser sees their output, not their implementation. With no client boundary in sight, the pipeline is short — and nothing about Page is ever sent to the browser.

⧉ static · Page() → React elements → ( RSC result + HTML ) → browser · no browser module, no hydration
what the server runs
export default function Page() {
  return (
    <main>
      <h1>Hello from the server</h1>
      <p>This is a Server Component.</p>
    </main>
  )
}
what the browser receives
<!-- result, not implementation -->
<main>
  <h1>Hello from the server</h1>
  <p>This is a Server Component.</p>
</main>

// no Page.js · no hydration · 0 KB
lock this in Server Component implementation is server-only. Server Component output is HTML + Flight. The browser never receives async function Page() — it receives the <main> the function returned.

05 The demystification layer · build time

6.1 + 6.2 · one source becomes two build artifacts

Build time writes a proxy and a manifest

When a Server Component imports a Client Component, the build does not use the original client implementation as ordinary server code. The next-flight-loader emits a registered reference, and a manifest plugin records where the real module lives. Press play to watch app/Counter.tsx become both.

▶ "use client" source → flight loader → registerClientReference proxy + manifest entry
00 / 00
FRAME 00 · —

Press play to compile the client boundary

The server-side value is not the real component. It is a registered reference with enough identity for React Flight to serialize it — plus a manifest row mapping that identity to a module id and chunks.

6.2 · what the loader emits for the server
import { registerClientReference } from "react-server-dom-webpack/server"

export default registerClientReference(
  function () {
    throw new Error("Attempted to call the default export from the server...")
  },
  "/app/Counter.tsx",   // resource key
  "default"             // export name
)

So <Counter initialCount={0} /> becomes, during the RSC render, an element whose type is ClientReference("/app/Counter.tsx", "default") with props: { initialCount: 0 }. The throwing function body is the point: a client export can only be rendered as a Component or passed as props to one — never invoked as server code.

6.1 · the client reference manifest entry
type ClientReferenceManifestEntry = {
  id: string | number     // e.g. 4872
  name: string             // e.g. "default" / "*"
  chunks: Array<string>    // chunk ids + filenames
  async?: boolean
}
where the manifest lives The manifest plugin emits per-route files like server/app/[route]_client-reference-manifest.js, which assign into globalThis.__RSC_MANIFEST[route]. That is server/build metadata for the rendering pipeline — not a public browser API you read by hand. Don't memorize the exact keys (clientModules, ssrModuleMapping, …); memorize the shape: source file + export → module id → chunk filenames → export name → async marker.

06 The demystification layer · server render

6.3 · resolveClientReferenceMetadata

The render turns a reference into a loading tuple

When React Flight serializes a client reference, it must turn /app/Counter.tsx + default into concrete loading metadata: a module id, chunk files, an export name, and an async marker. React's server-side webpack Flight config has resolveClientReferenceMetadata — it takes the manifest plus the reference, looks up the reference's $$id, and returns [id, chunks, name]. Press play to follow the lookup.

▶ ClientReference $$id → manifest lookup → Flight metadata [ id, chunks, name ]
00 / 00
FRAME 00 · —

Press play to resolve the reference

The reference's $$id is the lookup key. The manifest is the table. The tuple is the answer, and it gets encoded into the Flight stream at the boundary's position, alongside the serialized props.

ClientReference { $$id }→ lookup → manifest entry { id, chunks, name }→ encode → [ id, chunks, name ]
// the resolved Flight metadata for Counter
[ 4872, ["123", "static/chunks/app/counter-abc123.js"], "default" ]

// async modules carry a 4th element:
[ 4872, [ …chunks… ], "default", 1 ]
that is the bridge The source module/export has now been converted into a small array the browser can act on mechanically. No guessing remains: 4872 is the module id, the strings are the chunks to load, "default" is the export to read. This tuple is the thing that travels.

07 The demystification layer · browser

6.4 · preload → require → read → hydrate

The browser decodes the tuple and loads the real module

On the browser side, React's Flight client receives metadata for a client reference. It can use the already-resolved metadata directly: resolveClientReference returns [id, chunks, name], preloadModule loads the chunks, and requireModule calls __webpack_require__(metadata[ID]) before returning the requested export. The client never inspects the DOM and never reads the manifest file — it acts on metadata that arrived in the stream. Press play.

▶ Flight metadata → preloadModule → loadChunk → __webpack_require__(id) → export → hydrateRoot
00 / 00
FRAME 00 · —

Press play to load and hydrate Counter

Decode the metadata, preload the chunk, require the module by id, read the export — and only then does the painted button become a live React component with working useState and onClick.

// Flight says
module id: 4872
chunks: ["123", "static/chunks/app/counter-abc123.js"]
export: "default"

// React Flight client
preloadModule(metadata)
  → loadChunk("123", "static/chunks/app/counter-abc123.js")
requireModule(metadata)
  → __webpack_require__(4872)
  → moduleExports["default"]
  → the actual Counter function
the four locked-in roles Server reference: "Counter exists at this module/export." Manifest: "that module/export is this id and these chunks." Flight payload: "at this position, use that id/chunks/export with these props." Browser runtime: "load those chunks, require that id, read that export, hydrate it."

08 The transport, in one minute

self.__next_f · the queue, not the resolver

self.__next_f.push(...) is the courier

On initial load the browser gets one text/html response, and Next embeds the Flight data as inline scripts. The first push seeds a bootstrap segment into self.__next_f; later chunks push Flight data. The array buffers segments until Next's app runtime loads, drains the backlog, and patches .push so future chunks pipe straight into the Flight reader. The metadata tuple from the last section rides inside these chunks — the array carries it, React's decoder resolves it.

▶ one text/html response → self.__next_f buffers → .push is patched → createFromReadableStream → hydrateRoot
00 / 00
FRAME 00 · —

Press play to watch the bytes arrive and become a component

One HTTP response carries both the painted HTML and the Flight data as inline scripts. The array buffers the segments until the runtime drains them into the Flight reader, then hands the model to hydrateRoot.

zoom in · what actually rides inside one chunk

▶ one push string → rows → the I-row is [id, chunks, name] → $L points the model at it → resolve
00 / 00
FRAME 00 · —

A Flight chunk is just newline-delimited rows

Each row is id:tag JSON. The I row carries the client-reference tuple [id, chunks, name]; the model points at it with a lazy $L reference the decoder resolves on demand — preload the chunk, require the module, swap in the real component.

subsequent navigations differ On a client-side navigation, the browser fetches only the component payload with an rsc: 1 request header — no HTML document is transferred. The transport changes; the resolution machinery (metadata → preload → require → read) stays exactly the same.

09 The cast of the bridge

tap any piece to open its role

Every piece, and the one job it does

The bridge is not one mechanism — it is a relay of small, boring, specific artifacts, each with a single responsibility. Tap a card to see what it is, why it matters, and how it looks under the hood.

10 What the bridge is not

three corrections

Common confusions, corrected

Most misunderstandings of RSC come from collapsing two of the three identities, or from imagining the browser does something clever it never does. Tap each to see the correction.

11 The shortest correct version

The whole bridge, in one paragraph

In an RSC app, Server Components execute on the server and produce a rendered RSC result. When the render reaches a Client Component, it does not execute that client module as ordinary server-owned code — the build replaced that import with a registered client reference / proxy. React Flight serializes that reference using the client reference manifest, turning the source module/export into a module id, chunk filenames, export name, and props. Next streams the resulting Flight payload to the browser, often through inline self.__next_f.push(...) scripts during the initial HTML response. The client runtime converts those pushed chunks into a readable Flight stream, React decodes the module metadata, preloads the referenced chunks, requires the module by id, reads the export — and then React DOM hydrates the existing HTML.

the final lock-in A client component reference never means "the browser guesses which component to use." It means the server serialized enough bundler metadata for the browser runtime to load the exact JS chunk, require the exact module id, and read the exact export.
and the most important correction The client graph does not execute as normal server-owned RSC code during the RSC render. Instead: the server sees a registered client reference/proxy, React Flight resolves it through manifest metadata, the Flight payload carries the resolved loading information, and the browser runtime uses that information to load and hydrate the actual Client Component.
Back to JIT-Atlas