Lesson 02 · Advanced frontend rendering

Hydration is a contract

Estimated time: about 12 minutes

Server HTML is a snapshot, not an interactive application. Hydration succeeds only when the client can reproduce that snapshot before taking control.

12 minMechanics + diagnosis

The dangerous gap

SSR can improve when content becomes visible without improving when custom interactions become available. A user may see a convincing button before its component code is downloaded and managed by the client runtime.

HTML produced

The server creates a snapshot from data and component output.

HTML painted

The browser parses and displays it. Native behavior may already work.

Code available

Framework and component JavaScript arrive, parse, and execute.

Tree hydrated

The runtime matches the tree, restores state, and enables handlers.

Feel the “looks ready” trap

Advance a slow page through its loading lifecycle.

Noise-cancelling headphones
In stock · 1,799 kr.

What hydration actually pays for

Reconstruct

Execute component code to rebuild the client-side representation of the tree and recover state relationships.

Match

Associate that representation with existing DOM. React expects the server and initial client output to be identical.

Activate

Commit the managed tree so effects, subscriptions, state updates, and event behavior can operate.

Interview correction: hydration is not “adding event listeners” only. That shorthand hides code execution, tree reconstruction, state restoration, matching, and effects—the work that can block the main thread.

Selective hydration changes the unit

Traditional whole-root hydration makes unrelated code part of one startup dependency. In React’s streaming architecture, Suspense boundaries can become hydration units: ready regions progress independently, and interaction raises a boundary’s priority.

Streaming: server → network

Controls when HTML for a boundary is flushed. It can reveal useful UI before slower data resolves.

Selective hydration: DOM → managed UI

Controls which already-rendered boundary the client activates first. It can prevent unrelated code from blocking urgent interaction.

Suspense is the shared boundary

The same declarative boundary coordinates a server fallback, streamed replacement, code readiness, and hydration priority.

It is not zero work

Code and state still need to arrive. Boundaries schedule and isolate work; islands or Server Components may remove more client work.

Mismatch: the contract was broken

A mismatch is not merely noisy logging. React warns that it does not guarantee mismatched attributes will be patched; recovery costs performance and can attach behavior incorrectly. Typical causes are time, randomness, locale, browser-only APIs, invalid HTML nesting, or data changing between server render and client bootstrap.

Fragile: compute twice

function Price() {
  const formatted = new Intl
    .NumberFormat(undefined, {
      style: 'currency',
      currency: 'DKK'
    })
    .format(readLatestPrice());

  return <strong>{formatted}</strong>;
}

Stable: hydrate one snapshot

function Price({ initialPrice }) {
  const price = useLivePrice({
    initialSnapshot: initialPrice
  });

  return <strong>{
    formatDKK(price)
  }</strong>;
}

Repair hierarchy

  1. Make the first render deterministic. Serialize the exact server snapshot, locale, identifiers, and other markup-shaping inputs.
  2. Defer browser-only differences. Read storage, media queries, measurements, and clocks after hydration or behind a deliberate client-only boundary.
  3. Use a stable placeholder. If the server cannot know the value, render the same honest placeholder on both first passes.
  4. Suppress only the truly unavoidable leaf. suppressHydrationWarning is a one-level escape hatch, not a reconciliation strategy.

Diagnosis drill

Scenario: A stock page server-renders 102.40. Before JavaScript starts, the feed updates to 102.55. The client’s first render reads the live store and warns about a mismatch. Which correction should you lead with?

A senior-shaped interview answer

“SSR gave us visible HTML, but custom interaction is gated by code arrival and hydration. I’d measure the painted-to-interactive gap rather than treating LCP as proof that the page is ready.”

“The client’s initial state must come from the exact server snapshot. Once that boundary hydrates, it can subscribe and reconcile with the live feed. Reading the current store independently on both sides creates a race.”

“If a large comments widget delays the purchase controls, I’d split them into Suspense boundaries so React can hydrate the urgent interaction independently. I would still reduce its JavaScript; scheduling does not erase the work.”

Primary reading