Back to React
React
easy
mid

What is hydration in server side rendering?

Hydration is the process where the client React runtime takes over server-rendered HTML: it walks the existing DOM, attaches event listeners, and reconciles state. Server sends HTML for fast first paint; client `hydrateRoot()` makes it interactive. Mismatches between server and client markup throw hydration errors. Modern variants: partial hydration, streaming SSR, React Server Components.

7 min read·~5 min to think through

Hydration is the bridge between a server-rendered HTML page and a fully interactive React app.

The flow

  1. Server: React renders <App/> to an HTML string (renderToString / renderToPipeableStream). Sent to the browser.
  2. Browser: parses HTML → paints. The page is visible but not interactive — no event handlers attached yet.
  3. JS arrives: bundle downloads + executes.
  4. Hydrate: hydrateRoot(container, <App/>) walks the existing DOM, attaches listeners, reconstructs state.
  5. Interactive: app behaves like a normal SPA from here.
tsx
// server.ts
const html = renderToString(<App />);
res.send(`<!doctype html><html>...<div id="root">${html}</div>...`);

// client.ts
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('root')!, <App />);

Why hydrate instead of re-render

Re-rendering would throw away the server HTML, flash an empty screen, then redraw. Hydration reuses the DOM that's already painted, so visually nothing changes — only behavior is added.

Hydration mismatch

If the server and client produce different markup, React throws:

Hydration failed because the initial UI does not match what was rendered on the server.

Common causes:

  • new Date(), Math.random(), window.innerWidth in render — server doesn't know these.
  • Locale or timezone differences (server is UTC, client is local).
  • if (typeof window !== 'undefined') branches.
  • Client-only libraries that touch the DOM during render.

Fixes:

  • useEffect for client-only logic (runs after hydration).
  • <ClientOnly> wrapper / useId for stable IDs.
  • React 18: suppressHydrationWarning for tiny, unavoidable mismatches like timestamps.

Modern variants

  • Streaming SSR (React 18): server pipes HTML in chunks; <Suspense> boundaries flush as they resolve.
  • Selective hydration: React hydrates components in priority order; user interactions can jump the queue.
  • Partial hydration / Islands: only interactive components get JS (Astro, Fresh, Qwik).
  • React Server Components (RSC): never hydrate at all — server renders to a special format, client patches it in. Zero JS for purely server components.

Trade-offs

SSR + hydrateCSR (SPA)RSC
First paintFastSlowFast
TTISlow (waits for JS)SlowFaster (less JS)
Bundle sizeSame as CSRSameSmaller
Server complexityHighLowHigher

When to use

  • SEO-critical pages: news, marketing, ecommerce → SSR + hydrate or RSC.
  • Logged-in apps: CSR or hybrid (RSC for non-interactive parts).
  • Pure dashboards: CSR is fine.

Follow-up questions

  • What causes a hydration mismatch and how do you fix it?
  • What's the difference between selective and partial hydration?
  • Why are React Server Components a 'no-hydration' model?

Common mistakes

  • Using window/document during render — server crashes or mismatches on client.
  • Branching on typeof window — produces different trees each side.
  • Disabling hydration warnings instead of finding the real source of drift.

Performance considerations

  • Hydration is CPU-heavy: React walks the entire tree, attaches listeners, runs effects. For large pages this can block the main thread for hundreds of ms. Streaming + selective hydration mitigate by prioritizing interactive parts. RSC eliminates hydration cost for non-interactive subtrees entirely.

Edge cases

  • Time-based rendering (`new Date()`) shifts between server render and client hydrate — render on server, freeze, or hydrate after mount.
  • Streaming SSR boundaries can hydrate out of order — make components idempotent.
  • Third-party widgets that rewrite DOM in scripts will collide with hydration.

Real-world examples

  • Next.js Pages Router uses SSR + hydration. App Router uses RSC + selective hydration for client components. Remix uses streaming SSR. Astro/Fresh use island hydration. All are responses to 'hydration is too expensive for big pages'.

Senior engineer discussion

Senior framing: hydration is the cost of having a JS framework drive HTML that already exists. The industry is moving toward minimizing it — RSC, islands, qwik's resumability. Knowing why hydration is expensive (event delegation setup, effect runs, tree walk) tells you why these new approaches matter.

Related questions