Hydration is the moment a server‑rendered React tree becomes interactive in the browser. Two patterns help us ship less JS and unblock the main thread:
- Progressive Hydration – hydrate over time.
- Selective Hydration – hydrate what matters first.
Progressive Hydration
Progressive hydration defers work until a component actually needs to be interactive—for example when it scrolls into view. Instead of hydrating the whole page at once, we hydrate in slices, scheduling each slice when the browser is idle or a trigger fires. This spreads CPU cost and reduces the Time‑to‑Interactive (TTI).
Simplified Example
// Hydrator.js
import { hydrateRoot } from "react-dom/client";
export function hydrateWhenVisible(id) {
const el = document.getElementById(id);
if (!el) return;
// Only hydrate once the element is in the viewport
const io = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
hydrateRoot(el, <App />);
io.disconnect();
}
});
io.observe(el);
}
<!-- index.html -->
<div id="users" data-hydrate="visible">
<!-- SSR'ed markup for <Users /> -->
</div>
<script type="module">
import { hydrateWhenVisible } from "/Hydrator.js";
hydrateWhenVisible("users");
</script>
The Users list stays HTML‑only until the user scrolls to it, then we mount React.
Selective Hydration
Selective hydration arrived with React 18+ streaming SSR. When React streams HTML, it can immediately hydrate chunks that have already arrived, without waiting for the entire page. Even better, if the user interacts with an already‑rendered—but not yet hydrated—portion, React prioritizes that slice.
Simplified Example
// server.jsx
import { renderToPipeableStream } from "react-dom/server";
import express from "express";
const app = express();
app.get("/", (req, res) => {
const { pipe } = renderToPipeableStream(
<App />,
{
onAllReady() {
res.setHeader("Content-Type", "text/html");
pipe(res);
},
}
);
});
// client.jsx streamed chunk
<Suspense fallback={<Spinner />}>
<Comments /> {/* arrives later */}
</Suspense>
If the user taps inside <Comments> before its JS arrives, React hydrates that subtree first, keeping the interaction snappy.
Pros & Cons
| Pattern | Pros | Cons |
|---|---|---|
| Progressive | • Defers JS & CPU • Excellent for below‑the‑fold content • Works in React ≤17 with small libs |
• Manual; you must decide triggers • First interaction may still load JS • Multiple idle callbacks can complicate state sync |
| Selective | • Automatic priority on user interaction • Plays well with streaming SSR/Suspense • Reduces main‑thread blocking for critical UI |
• React 18+ only • Needs streaming infra (Node & CDN that supports flush())• Debugging hydration order can be tricky |
When to Use What?
- Use Progressive Hydration if you have large, non‑critical widgets (maps, big lists, ads) and you’re on a React version before 18 or can’t stream HTML.
- Use Selective Hydration if you already stream with React 18+ and want React to automatically prioritize interactivity.
Conclusion
Both patterns share a goal: ship less JavaScript upfront and make real user interactions faster.
- Progressive hydration lets you orchestrate hydration.
- Selective hydration lets React orchestrate hydration based on what the user does.
Mix‑and‑match: you can progressively hydrate low‑priority widgets while React selectively hydrates streamed chunks. The result is a faster, more resilient UX—exactly what modern users (and Core Web Vitals) demand.
Further Reading
- Lydia Hallie & Addy Osmani – Learning Patterns (Hydration section)
Top comments (0)