Partial Hydration and Islands Architecture
The Full Hydration Problem
Traditional SSR frameworks (Next.js Pages Router, Remix, Nuxt) hydrate the entire page. Every component — even static headings, paragraphs, and images — downloads JavaScript, re-executes on the client, and hooks into React's runtime.
For a typical content page:
- 90% of components are static (text, images, layout)
- 10% are interactive (buttons, forms, dropdowns)
- 100% of components ship JavaScript and hydrate
That's insane. You're downloading and executing JavaScript for hundreds of components just to make a few buttons clickable.
Imagine electrifying a house. Full hydration is like running electricity to every single item — the couch, the bookshelf, the paintings on the wall — even though only the lamp, TV, and refrigerator actually need power. Islands architecture only wires up the things that need electricity. Everything else is already "rendered" (it's furniture — it just sits there looking correct).
Islands Architecture
The islands concept, popularized by Jason Miller (of Preact fame) and implemented by Astro, flips the default:
- Static HTML is the default. The page is rendered on the server as pure HTML with zero JavaScript.
- Interactive "islands" opt in to JavaScript. Only the components that need interactivity get hydrated.
- Each island is independent. They hydrate separately, with their own framework instance if needed.
┌──────────────────────────────────────────┐
│ Static HTML (no JS) │
│ ┌──────────────────────────────────────┐│
│ │ Navigation ││
│ └──────────────────────────────────────┘│
│ │
│ ┌───────────┐ Static text... │
│ │ Search │ More static text... │
│ │ Island │ No JavaScript here. │
│ │ (React) │ │
│ └───────────┘ │
│ │
│ Static content continues... │
│ │
│ ┌──────────────────────────────────────┐│
│ │ Comments Island (Svelte) ││
│ │ Like button, reply form, etc. ││
│ └──────────────────────────────────────┘│
│ │
│ Static footer (no JS) │
└──────────────────────────────────────────┘
Notice: each island can use a different framework. The Search island is React. The Comments island is Svelte. The rest of the page is zero-JS HTML. This is possible because islands are independent — no shared runtime.
How Astro Implements Islands
Astro is the poster child for islands architecture. Every component is static by default. You explicitly opt into client-side JavaScript with a client:* directive.
---
import Header from './Header.astro'
import SearchBar from './SearchBar.tsx'
import BlogContent from './BlogContent.astro'
import CommentsSection from './CommentsSection.tsx'
import Footer from './Footer.astro'
---
<Header />
<SearchBar client:load />
<BlogContent />
<CommentsSection client:visible />
<Footer />
The client:* directives control when the island hydrates:
client:load— Hydrate immediately when the page loads. For above-the-fold interactive elements.client:idle— Hydrate when the browser is idle (requestIdleCallback). For lower-priority interactivity.client:visible— Hydrate when the element scrolls into view (Intersection Observer). For below-the-fold content.client:media— Hydrate when a CSS media query matches. For responsive interactivity (e.g., mobile menu).client:only— Skip SSR entirely, render only on the client. For components that can't server-render.
Partial Hydration vs Islands
These terms are related but distinct:
Partial hydration is the concept — hydrating only parts of the page instead of everything. It's a strategy.
Islands architecture is one implementation of partial hydration — independent, isolated interactive regions in a sea of static HTML.
React's selective hydration (via Suspense boundaries) is a different implementation — it hydrates the whole page, but does it progressively by priority, allowing user interactions to bump hydration order.
| Aspect | Full Hydration | React Selective Hydration | Islands (Astro) |
|---|---|---|---|
| JS shipped | All component JS | All component JS | Only island JS |
| Hydration scope | Entire page | Entire page (prioritized) | Islands only |
| Static components | Hydrated (wastefully) | Hydrated (in priority order) | Never hydrated (zero JS) |
| Framework overhead | Full React runtime | Full React runtime | Per-island (can be zero) |
| Interactivity timing | After full hydration | Priority-based (user clicks bump priority) | Per-island (load/idle/visible) |
| State sharing | Easy (shared React tree) | Easy (shared React tree) | Hard (islands are isolated) |
| Best for | Highly interactive apps | Interactive apps with clear sections | Content-heavy sites with sparse interactivity |
The Tradeoff: Isolation
Islands are independent. That independence is both their superpower and their limitation.
The superpower: Each island loads, hydrates, and renders independently. A broken island doesn't crash the page. A slow island doesn't block fast ones. You can even mix frameworks.
The limitation: Islands can't easily share state. In a React SPA, components share state through context, prop drilling, or state managers. In islands architecture, each island has its own React instance — there's no shared context tree.
// This doesn't work across islands:
// Island A: ThemeProvider sets theme
// Island B: useTheme reads it
// They're separate React trees — context doesn't connect them
Solutions for cross-island communication:
- Custom events:
document.dispatchEvent(new CustomEvent('theme-change', { detail: 'dark' })) - Shared store: A vanilla JS store (like nanostores) that islands subscribe to independently
- URL state: Use URL search params for shared state (works across any framework)
- DOM attributes: Write to a
data-*attribute that other islands observe
Don't choose islands architecture for apps where components need to share lots of state — dashboards, social media feeds, collaborative tools. The communication overhead between isolated islands becomes worse than just shipping a unified React runtime. Islands shine for content sites (blogs, docs, marketing) where 90%+ of the page is static and the few interactive pieces are self-contained.
React Server Components vs Islands
RSC and islands solve the same problem (ship less JS) through different mechanisms:
RSC approach: One unified React tree. Server Components are resolved on the server and their output is embedded in the RSC payload. Client Components hydrate within the same React tree. State sharing works normally.
Islands approach: Multiple independent trees. Each island is its own mini-app. No shared React context. Static HTML is the connective tissue.
RSC mental model:
One big React tree
├── Server Component (no JS)
├── Server Component (no JS)
├── Client Component (ships JS, hydrates)
├── Server Component (no JS)
└── Client Component (ships JS, hydrates)
Islands mental model:
Static HTML page
├── Static HTML
├── [Island: React mini-app] (ships JS, hydrates)
├── Static HTML
├── [Island: Svelte mini-app] (ships JS, hydrates)
└── Static HTML
RSC keeps the single-tree model React developers know. Islands break it into independent pieces. RSC is better for interactive apps; islands are better for content-heavy sites.
| What developers do | What they should do |
|---|---|
| Use islands architecture for highly interactive applications Islands are isolated — cross-island state sharing requires workarounds. Interactive apps benefit from a unified component tree. | Use RSC or a full SPA for apps where components frequently share state |
| Hydrate every island with client:load The whole point of islands is deferring JS. client:load on everything is just full hydration with extra steps. | Use client:visible for below-the-fold, client:idle for non-critical, client:load only for above-the-fold interactive elements |
| Think RSC and islands are the same thing RSC allows normal React composition and state sharing. Islands are isolated — different mental model, different tradeoffs. | RSC keeps one React tree with server/client component split. Islands create independent mini-apps in static HTML. |
| Dismiss islands because you prefer React They solve different problems. A documentation site benefits more from islands. A dashboard benefits more from RSC. Pick based on the use case. | Use Astro with React islands for content sites, Next.js with RSC for interactive apps |
- 1Full hydration hydrates everything. Partial hydration hydrates only interactive parts.
- 2Islands architecture renders static HTML by default. Interactive components opt in to JavaScript.
- 3Each island is independent — separate hydration, separate framework instance, no shared state.
- 4Astro's client:* directives control when islands hydrate: load, idle, visible, media, or only.
- 5Islands are best for content-heavy sites (blogs, docs, marketing). RSC is better for interactive apps.
- 6Cross-island state sharing requires custom events, shared vanilla stores, or URL state — React context doesn't work.