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.
Hydration is the bridge between a server-rendered HTML page and a fully interactive React app.
The flow
- Server: React renders
<App/>to an HTML string (renderToString/renderToPipeableStream). Sent to the browser. - Browser: parses HTML → paints. The page is visible but not interactive — no event handlers attached yet.
- JS arrives: bundle downloads + executes.
- Hydrate:
hydrateRoot(container, <App/>)walks the existing DOM, attaches listeners, reconstructs state. - Interactive: app behaves like a normal SPA from here.
// 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.innerWidthin 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:
useEffectfor client-only logic (runs after hydration).<ClientOnly>wrapper /useIdfor stable IDs.- React 18:
suppressHydrationWarningfor 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 + hydrate | CSR (SPA) | RSC | |
|---|---|---|---|
| First paint | Fast | Slow | Fast |
| TTI | Slow (waits for JS) | Slow | Faster (less JS) |
| Bundle size | Same as CSR | Same | Smaller |
| Server complexity | High | Low | Higher |
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'.