Hydration Patterns: Selective vs Progressive Hydration
Hydration patterns are about controlling when interactivity becomes available after server rendering. The core trade-off is between implementation simplicity and how much JavaScript and main-thread work you force the browser to do up front.
Quick Navigation: What is Hydration? • The Real Problem: Full Hydration • Selective Hydration • Progressive Hydration • Selective vs Progressive Hydration • Best Practices • Real-World Examples
Quick Decision Guide
Hydration is the client-side work that makes server-rendered HTML interactive.
- Full hydration hydrates the whole tree eagerly - Selective hydration lets independent boundaries become interactive sooner - Progressive hydration is a broader loading strategy: spread hydration work by priority, visibility, or timing
Interview signal: fast HTML is not enough. The real question is how quickly the browser can execute JavaScript and make important UI interactive.
What is Hydration?
Hydration is React’s process of attaching client-side behavior to HTML that was already rendered on the server.
🔥 Insight
Hydration is not “showing the page.” The page is already visible. Hydration is what makes that visible HTML interactive.
How It Works
1. Server renders React output into HTML
2. Browser displays that HTML immediately
3. JavaScript for the page loads
4. React matches the existing DOM to the component tree
5. Event handlers and client-side behavior become active
<div id="root">
<button>Add to cart</button>
</div>Before hydration, the button may look complete but cannot yet run React event logic.
Why Hydration Can Hurt Performance
Common Misunderstanding
Developers often think SSR automatically means “fast.” It often improves first paint, but hydration cost still decides when the page truly behaves like an app.
The Real Problem: Full Hydration
In a naive setup, the entire interactive tree hydrates eagerly.
Mental Model
> Every component asks for browser time at once.
That creates a few common problems:
Example
A product page may contain:
If all of them hydrate immediately, the browser spends time on low-value work before finishing critical interactions.
Selective Hydration
Selective hydration means React can hydrate some boundaries before others instead of treating the whole page as one indivisible task.
🔥 Insight
Selective hydration is about shrinking the unit of work. If the page is divided into independent boundaries, React can make useful parts interactive sooner.
How It Works
React uses boundaries such as Suspense to break the tree into smaller units. Those boundaries can then hydrate independently when their code and data are ready.
This is especially useful when:
Important Clarification
Selective hydration is an official React optimization tied to Suspense boundaries. It is not the same as “only hydrate one random component.” It works best when the tree is intentionally divided into meaningful interactive units.
Example: Suspense Boundary
import { Suspense, lazy } from 'react';
const Reviews = lazy(() => import('./Reviews'));
export default function ProductPage() {
return (
<div>
<ProductHeader />
<AddToCartButton />
<Suspense fallback={<div>Loading reviews…</div>}>
<Reviews />
</Suspense>
</div>
);
}Why This Helps
ProductHeader and AddToCartButton can become interactive earlierReviews is isolated behind a boundaryNext.js App Router Interpretation
In Next.js, Server Components do not hydrate. Only Client Components participate in client-side JavaScript and hydration.
// app/page.jsx
export default function Page() {
return (
<div>
<StaticHeader />
<InteractiveButton />
<StaticContent />
</div>
);
}
// components/InteractiveButton.jsx
'use client'
import { useState } from 'react';
export default function InteractiveButton() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}Trade-offs
Pros
Cons
Best For
Pages where only some regions are interaction-critical: product pages, mixed content layouts, dashboards with heavy secondary widgets, and pages using Suspense-based splitting.
Progressive Hydration
Progressive hydration is a broader architectural pattern: hydrate interactivity in stages instead of forcing all client work up front.
Important Clarification
Unlike selective hydration, “progressive hydration” is usually used as a product or architecture term rather than a single React API name. The idea is to spread interactivity work by priority, visibility, or browser timing.
🔥 Insight
Selective hydration decides which boundaries can hydrate independently. Progressive hydration decides when hydration work should happen over time.
Common Progressive Strategies
1. Priority-Based Hydration
Hydrate critical UI first:
Hydrate lower-priority regions later:
2. Visibility-Based Hydration
Hydrate when a component approaches the viewport.
3. Idle-Time Hydration
Hydrate lower-priority components when the browser has spare time.
4. Streaming + Boundary-Based Reveal
Server-render and stream high-priority content first, then let later boundaries become interactive as their code and data arrive.
Example: Visibility-Based Hydration Shell
import { useEffect, useRef, useState } from 'react';
function LazyHydrate({ children }) {
const ref = useRef(null);
const [active, setActive] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
setActive(true);
observer.disconnect();
}
}, { rootMargin: '100px' });
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return <div ref={ref}>{active ? children : children}</div>;
}The exact implementation depends on framework structure, but the architectural goal is to postpone non-critical work.
Example: Streaming with Suspense
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<Header />
<AddToCartButton />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews />
</Suspense>
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations />
</Suspense>
</div>
);
}Trade-offs
Pros
Cons
Best For
Large pages with clear priority layers: e-commerce, landing pages with embedded tools, dashboards, complex article pages with widgets, and app shells with heavy below-the-fold content.
Selective vs Progressive Hydration
| Aspect | Full Hydration | Selective Hydration | Progressive Hydration |
|---|---|---|---|
| Core idea | Hydrate whole tree eagerly | Hydrate independent boundaries sooner | Spread hydration work by priority/timing |
| Interactivity | Often delayed by startup spike | Important areas can activate earlier | Important areas activate first, others later |
| JavaScript work | Front-loaded | Reduced contention | More distributed over time |
| Complexity | Lowest | Medium | Highest |
| Best fit | Small/simple pages | Mixed pages with clear boundaries | Large pages with clear priority tiers |
Best Practices
1. Hydrate Based on User Value
Hydrate these early:
Hydrate these later when possible:
2. Keep Client Boundaries Small
In frameworks like Next.js App Router, every 'use client' boundary pulls code into the client bundle. Move only truly interactive code to the client.
3. Use Suspense Intentionally
Suspense is not just for loading spinners. It also creates useful boundaries that React can treat independently during streaming and selective hydration.
4. Measure JavaScript, Not Just HTML
Track:
5. Avoid Hydration Mismatches
Hydration expects the initial client render to match server HTML.
Common causes of mismatch:
Math.random() during renderDate.now() or time-zone differenceswindow before mount// ❌ Mismatch-prone
function Component() {
return <div>{Math.random()}</div>;
}
// âś… Safer client-only reveal
import { useEffect, useState } from 'react';
function Component() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return <div>Loading...</div>;
return <div>{Math.random()}</div>;
}6. Prefer Server Components for Non-Interactive Regions
In Next.js App Router, Server Components reduce client JavaScript because they do not hydrate. That makes them a strong default for static or data-heavy UI with no browser-only behavior.
Real-World Examples
E-commerce Product Page
Pattern: selective + progressive
function ProductPage() {
return (
<div>
<ProductHeader />
<ProductImages />
<AddToCartButton />
<Suspense fallback={<DescriptionSkeleton />}>
<ProductDescription />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews />
</Suspense>
<LazyHydrate>
<RelatedProducts />
</LazyHydrate>
</div>
);
}Blog Post
Pattern: minimal hydration
Most of the page can remain server-rendered or static. Only hydrate the small interactive islands.
function BlogPost() {
return (
<article>
<PostHeader />
<PostContent />
<ShareButtons />
<CommentForm />
<PostFooter />
</article>
);
}Dashboard
Pattern: priority-based progressive hydration
Hydrate the shell and high-value controls first. Charts, large tables, and secondary widgets can activate later.
function Dashboard() {
return (
<div>
<DashboardHeader />
<QuickActions />
<Suspense fallback={<ChartSkeleton />}>
<AnalyticsChart />
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<DataTable />
</Suspense>
</div>
);
}