DEV Community

Cover image for Selective vs Progressive Hydration
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Selective vs Progressive Hydration

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);
}
Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)