skip to content

Why does a server-rendered route embed a block of serialized data in the HTML alongside the markup it already rendered?

level: juniorimportance: must knowfreq 62%

answer

  1. HTML is output, not input
  2. hydration re-runs the same code
  3. the browser needs identical inputs
  4. same data typically travels twice

basics

~20 s

Markup is output, not input - it never says which values produced it. The embedded payload hands the client the same data the server rendered from, so the client can rebuild the identical tree without scraping the DOM or refetching.

solid answer

~50 s

A server render produces two artifacts: the HTML a browser can paint immediately, and a serialized copy of the data that HTML was built from. The client needs the second one because hydration re-runs the same component code in the browser and a render is a function of its inputs. Reading the values back out of the DOM would be lossy and ambiguous - a cell showing `1,024` could have come from a number, a string or a formatted total, and nothing in the markup says which. So the framework writes the route's data into the document as text the client can read synchronously. The consequence is that the same information typically travels twice in one response: once as rendered elements, once as serialized data. That duplication buys a page that is painted and interactive from a single request, and it is why payload size is a real cost rather than an implementation detail.

go deeper

for a junior

Recall that a server-rendered document carries two things: the markup to paint and a serialized copy of the data behind it, which the client reads while taking over the page.

for a middle

Explain why the markup cannot be the data source - types are erased, formatting is one-way, and loaded values that were never displayed are simply absent - and name the duplication that results.

for a senior

Weigh the trade: the duplication buys paint plus interactivity from one request, at the price of putting every loaded field on the critical path for transfer and main-thread parse.

for a principal

Frame the block as a response the route publishes on every render. What is allowed into it deserves the same review as a public API body, because that is effectively what it is.

## The two artifacts of one server render When a route renders on the server, the response carries **two representations of the same information**. The first is the **markup**: elements, text and attributes the browser paints before any script runs. The second is a **serialized copy of the data that markup was produced from**, embedded in the document as text so the client can read it immediately, with no extra network round trip. The second artifact exists because of what happens after first paint. A server-rendered page is usually not finished when it appears: the same component code runs again in the browser to attach event handlers and take over updates. That step is **hydration**, and it re-executes a render. A render is a function of its inputs, so the browser needs those inputs. ## Why the markup cannot serve as the data source - **Types are erased.** Everything in the DOM is text. `1,024`, `1024` and `one thousand twenty-four` are indistinguishable as *types*, and a date rendered as `3 May` has lost its instant entirely. - **Rendering is lossy on purpose.** A route often loads values it never displays - an identifier used for a link target, a permission flag that decided whether a button rendered at all. Those values are gone from the markup. - **Formatting is one-way.** Currency, locale and truncation cannot be reliably inverted. - **Structure is flattened.** Relationships between records become nesting and ordering, which the client would have to guess at. Given all that, recovering inputs from output is an unreliable parse. Handing the inputs over directly is simply correct. ## What tends to be inside the block - The data the route's **server-side data step** returned for this URL. - The values handed into the **interactive parts** of the tree, so those parts hydrate with the props they rendered with. - **Routing and context state** the client runtime needs to continue navigation. - On a streamed response, **later chunks appended as they resolve**, so a slow region can fill in after the shell has already arrived. ## The cost that rides along | Cost | Why it applies | |---|---| | **Transfer bytes** | The payload is in the same response as the markup, so it sits on the critical path to interactivity. | | **Parse time** | It is read on the main thread before handlers attach; large blocks delay interactivity even on a fast connection. | | **Memory** | The parsed structure is retained for as long as the client runtime needs it. | | **Poor cacheability** | It reflects one render for one URL and often one user, so it is far less shareable than a code bundle. | This is why the block scales with **data**, not with code: adding one row or one column to what the route loaded adds bytes for every visitor, forever, whether or not the page shows it. ## Where frameworks differ The idea is universal; the execution is not. Some frameworks hand over the whole route's data as one block; others serialize per interactive island so a mostly static page ships almost nothing. Some re-execute components during hydration and need the inputs for that reason; others are designed to **resume from serialized state** instead of re-executing, which changes what has to be in the block but not the fact that something is. Frameworks also differ in how rich the encoding is - whether it is plain JSON or an extended format that can carry values JSON cannot. ## How to look at it yourself 1. View the document source of a server-rendered route and find the serialized block alongside the markup. 2. Compare the size of the whole document with the amount of text actually visible on screen. 3. Look for field names in the block that never appear anywhere in the rendered page - those are pure cost. That last check is the practical value of understanding this mechanism. Once you know the payload mirrors what the route loaded rather than what it displayed, an unexplained jump in document size has an obvious first suspect. ## The security corollary Because the block is in the document, it is **readable by anyone who can open the page** - no authentication, no developer tooling required, and copies of it live in any cache or prerendered file that holds the document. It is a public response body in every practical sense, and everything that ends up in it should be reviewed as one.

  • On a client-side navigation, where no new HTML document is fetched, is there still a payload?
    Yes, but typically without the duplication. The client asks for the new route's serialized data on its own and renders from it, so the markup is produced in the browser rather than sent. The byte cost of what the route loaded is still paid - it is simply not paired with rendered HTML that time.
  • If a route renders nothing interactive at all, does it still need a serialized block?
    Often not, or only a minimal one. If no part of the tree hydrates, there is no client render that needs the inputs. Frameworks that hydrate per island can ship a fully static route with no data block; frameworks that hydrate the whole page generally include one regardless.

A restaurant can serve you a finished dish, but if you want to cook it again yourself you need the recipe, not a photograph of the plate. The payload is the recipe shipped in the same box as the meal.

saying these in an interview costs you the question

  • Thinks hydration recovers its values by reading them back out of the DOM.
  • Assumes the block is private because a normal user never opens the page source.
  • Says server rendering means the client needs no data of its own.
  • Confuses compressing well on the wire with being free to parse and hold.
  • Calls the block a cache and expects it to refresh when server data changes.