Partial Hydration — Mixing Static and Interactive Content in SSR
In this tutorial, you will learn about Partial Hydration. We cover key concepts, practical examples, and best practices to help you master this topic.
Partial hydration renders most of the page as static HTML and only adds JavaScript interactivity to isolated components, reducing bundle size and improving performance.
What You'll Learn
By the end of this tutorial, you will understand what partial hydration is, how it differs from full hydration, the islands architecture pattern, how frameworks like Astro and Marko implement partial hydration, and how to build pages with partial hydration manually.
Why It Matters
On average, less than 30 percent of a page needs JavaScript interactivity. Full hydration sends JavaScript for the entire page, most of which is never used. Partial hydration sends JavaScript only for interactive islands, resulting in 50-80 percent smaller bundles, faster loads, and better performance on slow devices.
Real-World Use
Astro, a framework built on partial hydration, powers the website of a major SaaS company. Their landing page has 12 interactive components (navigation, forms, animations) out of 50+ total components. With partial hydration, they send only 35KB of JavaScript instead of 180KB. The page scores 98 on Lighthouse performance.
Partial Hydration vs Full Hydration
┌──────────────────────────────────────────────────────────┐
│ Full Hydration vs Partial Hydration │
├──────────────────────────────────────────────────────────┤
│ │
│ All components become Only interactive islands │
│ hydrated (JS attached) become hydrated │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ Header ████ │ │ Header (static) │ │
│ │ Nav ████ │ │ Nav (static) │ │
│ │ Hero ████ │ │ Hero (static) │ │
│ │ Form ████ │ │ Form (██ interactive)│ │
│ │ Cards ████ │ │ Cards (static) │ │
│ │ Footer ████ │ │ Footer (static) │ │
│ └──────────────────┘ └──────────────────────┘ │
│ │
│ JS: 200KB for all JS: 35KB for interactivity │
│ TTI: 4.2s TTI: 1.5s │
│ │
└──────────────────────────────────────────────────────────┘
Think of partial hydration like a coloring book. The entire page is printed in black and white (static HTML). Most of the page stays in black and white — it is perfectly readable and useful. Only specific elements get color (JavaScript hydration) — the parts that need it. Coloring every part of the book would waste time and crayons.
Implementing Partial Hydration with Astro
// Astro component — most of the page is static
---
// This code runs at build time or on the server
import Header from '../components/Header.astro';
import HeroSection from '../components/HeroSection.astro';
import ProductGrid from '../components/ProductGrid.astro';
import AddToCartButton from '../components/AddToCartButton.jsx';
import NewsletterForm from '../components/NewsletterForm.jsx';
---
<html>
<body>
<!-- Static components — no JavaScript -->
<Header />
<HeroSection />
<ProductGrid products={products} />
<!-- Interactive islands — JavaScript sent to client -->
<!-- client:load — load and hydrate immediately -->
<AddToCartButton
productId={product.id}
client:load
/>
<!-- client:idle — hydrate when browser is idle -->
<NewsletterForm client:idle />
<!-- client:visible — hydrate when visible in viewport -->
<LiveChat client:visible />
<!-- client:media — hydrate only on desktop -->
<DashboardWidget client:media="(min-width: 768px)" />
<!-- client:only — client-side only, no SSR -->
<UserPreferences client:only="react" />
<!-- Static footer -->
<footer>...</footer>
</body>
</html>
Manual Partial Hydration
// Manual partial hydration without a framework
// 1. Server renders full HTML
// 2. Identify interactive sections with data attributes
// 3. Manually hydrate only those sections
// Server output:
// <div id="root">
// <header>Static header</header>
// <main>
// <p>Static content — no JS needed</p>
// <div class="island" data-component="SearchForm">
// <form>
// <input type="search" />
// <button>Search</button>
// </form>
// </div>
// <p>More static content</p>
// <div class="island" data-component="ThemeToggle">
// <button>Toggle Theme</button>
// </div>
// </main>
// <footer>Static footer</footer>
// </div>
// Client hydration — only hydrate islands
import { createRoot, hydrateRoot } from 'react-dom/client';
const componentMap = {
SearchForm: () => import('./SearchForm'),
ThemeToggle: () => import('./ThemeToggle'),
};
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.island').forEach(island => {
const componentName = island.dataset.component;
const loader = componentMap[componentName];
if (loader) {
loader().then(module => {
const Component = module.default;
hydrateRoot(island,
React.createElement(Component, island.dataset)
);
});
}
});
});
Common Mistakes
- Making too many components interactive. Every interactive island adds JavaScript. Before adding interactivity, ask: does this component really need JavaScript, or can it work with CSS-only?
- Not considering SEO for interactive islands. Content inside interactive islands that render on the client may not be indexed. Ensure critical SEO content is in the static HTML.
- Breaking the page without JavaScript. Static content should be fully functional without JavaScript. Interactive islands are enhancements, not requirements.
- Inconsistent styling between static and interactive islands. CSS should be consistent whether components are static or interactive. Use utility classes or a shared stylesheet.
- Complex state management across islands. Islands are isolated. Sharing state between islands requires a global event system or a shared store, adding complexity.
Practice Questions
- What is the difference between full hydration and partial hydration?
- How does Astro implement partial hydration with client directives?
- How do you manually implement partial hydration without a framework?
- What type of content should remain static in partial hydration?
- How do you handle state sharing between hydrated islands?
Challenge: Build a landing page with Astro-style partial hydration: one interactive island (a newsletter signup form with validation), and the rest static HTML. Then build the same page with full hydration (React on the entire page). Compare JavaScript bundle size, TT I, and Lighthouse scores.
FAQ
Mini Project
Build a blog homepage with partial hydration: use Astro (or manual implementation) with static HTML for the blog post list, author bio, and footer. Add interactive islands for: search bar (client:load), newsletter popup (client:idle), share buttons (client:visible), and dark mode toggle (client:media). Measure JavaScript budget savings.
What's Next
You understand partial hydration. Now explore Islands Architecture for building interactive components in static HTML.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro