The RSC Payload and Hydration

The server does not send HTML alone. Alongside the markup it sends the RSC Payload: a compact, serialized description of the rendered Server Component tree, holding the elements those components produced, the props they passed down, and a placeholder plus a chunk URL for every Client Component in it.

Fetch the production page from the last subsection. The whole document is 4852 bytes: ordinary markup, then the payload, split across self.__next_f.push([1, "..."]) script tags so that it streams. Decoding those strings gives numbered rows, two of which matter here:

Two rows of the decoded RSC payload for /TSX
4:I[36498,["/_next/static/chunks/2t8b0zir_8762.js",
           "/_next/static/chunks/2ije1dv8oc_18.js"],"default"]
0:{... ["$","main",null,{"children":[
     ["$","h1",null,{"children":"Boundary demo"}],
     ["$","p",null,{"children":"Rendered on the server."}],
     ["$","$L4",null,{"start":3}]]}] ...}

Read it as a tree. ["$","main",null,{...}] is an element: a tag, a key, props. h1 and p are fully resolved — their text computed, the code that computed it gone. $L4 is the hole: a reference to client module 4, whose row names the chunks to download to fill it, and the props {"start":3} travel with the reference.

One response, three parts, and what each one is for
One response, three parts, and what each one is for

Hydration only ever touches the holes. React 7,897 walks the existing DOM, matches it against the tree the payload describes, and attaches the onClick handler to the <button> already on screen. If the browser's render disagrees with the server's, React logs a hydration mismatch and re-renders that subtree — which is why Date.now() in a Client Component's body is a bug waiting for a slow day.

On a client-side navigation there is no HTML step at all: the router fetches only the payload for the new route.