Hydration Patterns: Selective vs Progressive Hydration

Medium•

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 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

•JavaScript must still download, parse, and execute
•Hydrating large trees can block the main thread
•Fast HTML does not guarantee fast interaction
•Low-end devices feel hydration cost much more than desktop machines

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:

•High main-thread blocking during startup
•Slower time to interaction for important controls
•Wasted work on components the user may never touch
•Large bundle execution cost before the page feels responsive

Example

A product page may contain:

•header navigation
•image gallery
•add-to-cart button
•recommendations carousel
•reviews widget
•analytics widgets
•chat launcher

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:

•one part of the page is immediately important
•other parts are slow, heavy, or lower priority
•some code chunks arrive later than others

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 earlier
•Reviews is isolated behind a boundary
•heavy work is not forced into one startup spike

Next.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

•Faster interactivity for important UI
•Less startup contention on the main thread
•Better match between user priority and browser work
•Fits well with Suspense and code splitting

Cons

•Requires intentional boundary design
•Shared state across boundaries can become trickier
•Heavy Client Components still cost time once they hydrate
•Not every page is complex enough to justify the extra structure

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:

•nav
•search
•primary CTA
•checkout controls

Hydrate lower-priority regions later:

•recommendations
•reviews
•footers
•analytics widgets

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

•Better perceived responsiveness
•Critical actions become usable earlier
•Large pages feel less blocked during startup
•Matches user intent better than eager hydration

Cons

•More architectural complexity
•Harder reasoning about state timing and readiness
•More moving pieces during debugging
•Requires discipline in deciding what is actually critical

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

AspectFull HydrationSelective HydrationProgressive Hydration
Core ideaHydrate whole tree eagerlyHydrate independent boundaries soonerSpread hydration work by priority/timing
InteractivityOften delayed by startup spikeImportant areas can activate earlierImportant areas activate first, others later
JavaScript workFront-loadedReduced contentionMore distributed over time
ComplexityLowestMediumHighest
Best fitSmall/simple pagesMixed pages with clear boundariesLarge pages with clear priority tiers

Best Practices

1. Hydrate Based on User Value

Hydrate these early:

•primary navigation
•search
•forms
•checkout and conversion actions
•controls near the top of the page

Hydrate these later when possible:

•carousels below the fold
•related content
•recommendations
•chat widgets
•analytics-heavy modules

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:

•bundle size
•hydration time
•main-thread blocking
•input delay on important actions

5. Avoid Hydration Mismatches

Hydration expects the initial client render to match server HTML.

Common causes of mismatch:

•Math.random() during render
•Date.now() or time-zone differences
•browser-only APIs during server render
•conditionals that depend on window 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

•hydrate header, image gallery, and add-to-cart first
•defer reviews, recommendations, and secondary widgets
•use boundaries so heavy sections do not block critical actions
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>
  );
}

Key Takeaways

1Hydration makes server-rendered HTML interactive; it does not create the initial visible HTML
2Fast first paint and fast interactivity are different performance goals
3Selective hydration is React’s boundary-level optimization, commonly tied to Suspense
4Progressive hydration is a broader strategy for spreading hydration work over time
5The real performance bottleneck is often JavaScript execution and main-thread work, not HTML delivery
6Next.js Server Components reduce hydration cost because they do not hydrate
7Use early hydration for high-value interactions and delay low-priority widgets
8Hydration mismatches happen when server and client do not render the same initial output