Skip to content

Selective Hydration — Hydrating Only Interactive Components

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Selective Hydration. We cover key concepts, practical examples, and best practices to help you master this topic.

Selective hydration hydrates only the interactive parts of server-rendered pages, keeping static sections as pure HTML for minimal JavaScript and faster interactivity.

What You'll Learn

By the end of this tutorial, you will understand what selective hydration is, how React 18 supports it through Suspense and useId, how to implement selective hydration patterns, and how it reduces JavaScript bundle size by keeping static content unhydrated.

Why It Matters

Most of a page is static content that does not need JavaScript. Traditional SSR hydrates everything, wasting bandwidth and CPU on content that will never be interactive. Selective hydration only hydrates components that actually need interactivity, dramatically reducing the JavaScript sent to the browser.

Real-World Use

A documentation site with hundreds of pages used selective hydration. The main content (mostly markdown) was server-rendered and never hydrated. Only the search bar, navigation toggle, and copy-code buttons were interactive. JavaScript bundle size dropped from 240KB to 45KB. Pages loaded in under 1 second on 3G connections.

Selective Hydration Architecture
    ┌──────────────────────────────────────────────────────────┐
    │              Selective Hydration                         │
    ├──────────────────────────────────────────────────────────┤
    │                                                          │
    │  Server-Rendered (static HTML, no JS):                   │
    │  ┌────────────────────────────────────────────────┐     │
    │  │ Article content (h1, p, ul, code blocks)       │     │
    │  │ Author bio, Related articles                    │     │
    │  │ Footer links, Copyright                         │     │
    │  │ (These never hydrate — 0 KB JavaScript)        │     │
    │  └────────────────────────────────────────────────┘     │
    │                                                          │
    │  Selectively Hydrated (client JS attached):              │
    │  ┌────────────────────────────────────────────────┐     │
    │  │ Search bar (onChange, onSubmit)                │     │
    │  │ Mobile menu toggle (onClick)                   │     │
    │  │ Copy code button (onClick, clipboard API)      │     │
    │  │ Theme switcher (onClick, localStorage)         │     │
    │  │ (Small JS chunks, only for interactive elems) │     │
    │  └────────────────────────────────────────────────┘     │
    │                                                          │
    │  Result: Minimal JavaScript, fast interactivity         │
    │                                                          │
    └──────────────────────────────────────────────────────────┘

Think of selective hydration like a museum with both static exhibits and interactive kiosks. The paintings and sculptures (static HTML) do not need electricity — they are perfectly fine as they are. The interactive kiosks (buttons, search) need power (JavaScript) to function. Selective hydration only runs power to the kiosks, not to every painting in the museum.

Separating Interactive from Static Content

// Static Server Component — never sends JavaScript
function ArticleContent({ article }) {
    return (
        <article>
            <h1>{article.title}</h1>
            <div className="content">
                {article.paragraphs.map((p, i) => (
                    <p key={i}>{p}</p>
                ))}
            </div>
            <div className="author-bio">
                <p>Written by {article.author}</p>
            </div>
        </article>
    );
}

// Interactive component — client JavaScript needed
'use client';

function CopyCodeButton({ code }) {
    const [copied, setCopied] = useState(false);

    const handleCopy = async () => {
        await navigator.clipboard.writeText(code);
        setCopied(true);
        setTimeout(() => setCopied(false), 2000);
    };

    return (
        <button
            onClick={handleCopy}
            className="copy-btn"
            aria-label="Copy code to clipboard"
        >
            {copied ? 'Copied!' : 'Copy'}
        </button>
    );
}

// Parent — selective hydration
function BlogPost({ article }) {
    return (
        <div>
            {/* This component is pure HTML — no JavaScript */}
            <ArticleContent article={article} />

            {/* This component hydrates — JavaScript sent to client */}
            <CopyCodeButton code={article.code} />
        </div>
    );
}

When to Use Selective Hydration

// Components that NEED hydration (interactive):
const interactiveComponents = [
    'Forms with validation',
    'Buttons with onClick handlers',
    'Search boxes with autocomplete',
    'Modals and dialog boxes',
    'Dropdown menus',
    'Tab switchers',
    'Accordion/collapse widgets',
    'Carousels and sliders',
    'Toast notifications',
    'Real-time data (counters, timers)'
];

// Components that DO NOT need hydration (static):
const staticComponents = [
    'Article and blog content',
    'Product descriptions',
    'User profiles (read-only)',
    'Navigation links',
    'Footer content',
    'Headers and titles',
    'Images and captions',
    'Tables of data (read-only)',
    'Lists and bullet points',
    'Static SVG illustrations'
];

// Decision helper
function needsHydration(component) {
    // Does the component respond to user input?
    // Does it have state that changes after initial render?
    // Does it use browser-only APIs (localStorage, clipboard)?
    // Does it communicate with a server (fetch, WebSocket)?
    // If NO to all: it does not need hydration

    return component.usesState ||
           component.usesEffects ||
           component.hasEventHandlers ||
           component.usesBrowserAPIs;
}

Selective Hydration with Islands

// Islands architecture pattern
// Static HTML with islands of interactivity

// Page component — mostly static HTML
function ProductPage({ product }) {
    return (
        <div>
            {/* Static section — no JS needed */}
            <ProductImages images={product.images} />

            {/* Interactive island */}
            <Island>
                <AddToCartButton productId={product.id} />
            </Island>

            {/* Static section */}
            <ProductDetails product={product} />

            {/* Interactive island */}
            <Island>
                <ReviewForm productId={product.id} />
            </Island>

            {/* Static section */}
            <RelatedProducts products={product.related} />
        </div>
    );
}

// Island component — marks interactive boundaries
function Island({ children }) {
    return (
        <div data-island>
            {children}
        </div>
    );
}

// Framework like Astro or Marko handles this automatically
// The framework detects islands and only sends JS for those components
// Static components are never hydrated — pure HTML

Common Mistakes

  1. Making everything interactive by default. It is easier to make components interactive than to decide which need it. But every interactive component adds JavaScript. Default to static, opt in to interactive.
  2. Hydrating interactive islands too early. Even interactive components can defer non-critical initialization. Only hydrate what the user can immediately interact with.
  3. Not measuring the static-to-interactive ratio. Aim for at least 60 percent of your page being static (no JS). Measure the JavaScript sent for each component.
  4. Breaking layout with hydration islands. Interactive islands must not change the layout when they hydrate. Use fixed dimensions and placeholder sizes.
  5. Forgetting SEO meta tags in interactive components. Meta tags in components that only render on the client (not SSR) will not be seen by search engines.

Practice Questions

  1. What is the difference between selective hydration and progressive hydration?
  2. Which components should be selectively hydrated vs kept static?
  3. How do you measure which components need JavaScript?
  4. What is the islands architecture and how does it relate to selective hydration?
  5. How do you ensure static content that later becomes interactive does not break layout?

Challenge: Build a product page and identify which components need hydration and which can remain static. Separate them into two groups. Implement selective hydration so that only the Add to Cart button, review form, and search bar send JavaScript. Measure the JavaScript bundle size saved compared to hydrating everything.

FAQ

Is selective hydration the same as partial hydration?

Selective hydration focuses on choosing which components to hydrate based on interactivity needs. Partial hydration (islands) is a broader concept where the framework renders islands of interactivity in a sea of static HTML.

Does selective hydration work with all frameworks?

Selective hydration is primarily associated with React 18 and frameworks built on it. Astro uses a similar concept (islands). Other frameworks have their own approaches.

How do I handle navigation with selective hydration?

Global interactive elements (navigation, search) need hydration. Use React.lazy for route components that are mostly static, and selective hydration for interactive widgets.

Can I selectively hydrate without a framework?

Yes. You can manually decide which parts of the page use React and which are plain HTML. Use multiple React roots for interactive sections and raw HTML for static content.

Does selective hydration improve Lighthouse scores?

Yes. Less JavaScript means faster load times, lower Total Blocking Time, and better Time to Interactive — all of which improve Lighthouse Performance scores.

Mini Project

Build a blog with selective hydration: the article body is static HTML (no React, no hydration), the search bar is an interactive island (React with hydration), the comments section is another island, and the theme switcher is a small interactive widget. Use separate React roots for each island. Measure JavaScript sent to the client.

What's Next

You understand selective hydration. Now explore Partial Hydration for mixing static and interactive content.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro