Skip to content

Partial Hydration and Islands Architecture

advanced15 min read

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.

Mental Model

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.

Quiz
A documentation page has 50 components. 3 are interactive (search bar, theme toggle, code playground). With islands architecture, how many components ship JavaScript?

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.
Quiz
A blog post has a comments section below the fold. Which Astro client directive is most appropriate?

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.

AspectFull HydrationReact Selective HydrationIslands (Astro)
JS shippedAll component JSAll component JSOnly island JS
Hydration scopeEntire pageEntire page (prioritized)Islands only
Static componentsHydrated (wastefully)Hydrated (in priority order)Never hydrated (zero JS)
Framework overheadFull React runtimeFull React runtimePer-island (can be zero)
Interactivity timingAfter full hydrationPriority-based (user clicks bump priority)Per-island (load/idle/visible)
State sharingEasy (shared React tree)Easy (shared React tree)Hard (islands are isolated)
Best forHighly interactive appsInteractive apps with clear sectionsContent-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
Common Trap

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.

Quiz
A marketing agency builds mostly content websites with occasional interactive forms. Which approach best fits their use case?
What developers doWhat 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
Key Rules
  1. 1Full hydration hydrates everything. Partial hydration hydrates only interactive parts.
  2. 2Islands architecture renders static HTML by default. Interactive components opt in to JavaScript.
  3. 3Each island is independent — separate hydration, separate framework instance, no shared state.
  4. 4Astro's client:* directives control when islands hydrate: load, idle, visible, media, or only.
  5. 5Islands are best for content-heavy sites (blogs, docs, marketing). RSC is better for interactive apps.
  6. 6Cross-island state sharing requires custom events, shared vanilla stores, or URL state — React context doesn't work.