Partial Hydration and Islands

Engineering
Share on LinkedIn Share on X Share on Reddit Share on HN Share on Bluesky

title: "Partial Hydration and Islands" slug: "web-islands-partial-hydration" description: "Reduce JavaScript with partial hydration and islands architecture: selective interactivity, Astro islands, framework components, and performance tradeoffs." datePublished: "2026-05-06" dateModified: "2026-07-17" tags:



title: "web-islands-partial-hydration" slug: "web-islands-partial-hydration" description: "" datePublished: "2026-07-17" dateModified: "2026-07-17" tags:



title: "web-islands-partial-hydration" slug: "web-islands-partial-hydration" description: "" datePublished: "2026-07-17" dateModified: "2026-07-17" tags:



title: "web-islands-partial-hydration" slug: "web-islands-partial-hydration" description: "" datePublished: "2026-07-17" dateModified: "2026-07-17" tags:



title: "web-islands-partial-hydration" slug: "web-islands-partial-hydration" description: "" datePublished: "2026-07-17" dateModified: "2026-07-17" tags:



title: "web-islands-partial-hydration" slug: "web-islands-partial-hydration" description: "" datePublished: "2026-07-17" dateModified: "2026-07-17" tags:



title: "Partial Hydration and Islands" slug: "web-islands-partial-hydration" description: "Reduce JavaScript with partial hydration and islands architecture: selective interactivity, Astro islands, framework components, and performance tradeoffs." datePublished: "2026-05-06" dateModified: "2026-07-17" tags:



Our marketing site shipped 180KB of JavaScript. The only interactive elements were a mobile nav toggle, a newsletter signup form, and a pricing calculator buried below the fold. Full-page React hydration ran JavaScript on hero text that never changed. Switching to islands architecture cut the initial JS payload to 24KB — three hydrated components on a page of static HTML.

The hydration problem

Traditional SSR with React, Vue, or Svelte:

  1. Server renders HTML
  2. Client downloads entire framework bundle
  3. Client re-renders (hydrates) every component
  4. Page becomes interactive

Step 3 runs JavaScript on static content — navigation chrome, blog prose, footers. That work blocks the main thread and delays interactivity on elements that actually need it.

Islands architecture

┌─────────────────────────────────────┐
│  Static HTML (no JS)                │
│  ┌─────────┐  ┌─────────────────┐    │
│  │ Header  │  │  Blog content   │    │
│  │ (static)│  │  (static)       │    │
│  └─────────┘  └─────────────────┘    │
│                                     │
│  ┌──────────────┐  ┌─────────────┐  │
│  │ 🏝 Search    │  │ 🏝 Cart     │  │
│  │ (hydrated)   │  │ (hydrated)  │  │
│  └──────────────┘  └─────────────┘  │
│                                     │
│  Footer (static)                    │
└─────────────────────────────────────┘

Each island is an independent interactive unit with its own JavaScript bundle.

Astro islands example

---
// src/pages/blog/post.astro
import Layout from '../layouts/Layout.astro';
import CommentForm from '../components/CommentForm.tsx';
import ShareButtons from '../components/ShareButtons.svelte';
---

<Layout title="My Post">
  <article>
    <h1>Understanding Islands</h1>
    <p>Most of this page is static HTML...</p>
  </article>

  <!-- Hydrate when visible in viewport -->
  <CommentForm client:visible />

  <!-- Hydrate on first interaction -->
  <ShareButtons client:idle />
</Layout>

Astro directives control hydration timing:

Directive When it hydrates
client:load Immediately on page load
client:idle When browser is idle
client:visible When scrolled into viewport
client:media When media query matches
client:only Client-only, no SSR

Partial hydration in React

React Server Components (RSC) achieve a similar split:

// Server Component (default) — no client JS
async function BlogPost({ slug }) {
  const post = await db.posts.find(slug);
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.body }} />
      <LikeButton postId={post.id} /> {/* Client Component */}
    </article>
  );
}

// Client Component — hydrated on client
'use client';
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);
  return <button onClick={() => setLiked(!liked)}>❤️</button>;
}

Server Components render on the server and never ship JavaScript. Only 'use client' components hydrate.

Measuring the impact

Compare full hydration vs. islands on the same page:

Metric Full hydration Islands
JS bundle (gzip) 180 KB 24 KB
Time to Interactive 4.2s 1.1s
Main thread work 890ms 210ms
Lighthouse Performance 62 94

Use Chrome DevTools Coverage tab to identify JavaScript that runs on static content.

Choosing what to hydrate

Hydrate components that need:

Keep static:

Tradeoffs

Pros: Smaller JS bundles, faster TTI, better Core Web Vitals, simpler static hosting.

Cons: Inter-island communication requires custom events or shared stores. Complex client state spanning multiple islands is harder than a unified SPA. Some frameworks have limited island support.

Islands work best for content-heavy sites with pockets of interactivity — marketing pages, blogs, documentation, e-commerce product pages.

Passing data to islands

Islands receive server-rendered props as HTML attributes or embedded JSON:

<Chart client:visible data={JSON.stringify(chartData)} />

Parse on hydration. Keep payloads small — large data sets should fetch client-side after the island becomes visible.

SEO considerations

Static HTML in islands architecture is fully crawlable. Ensure critical content — headings, product descriptions, links — lives in static HTML, not client-only islands. Interactive enhancements hydrate on top without hiding content from crawlers.

Client island communication

Islands sharing state without full SPA:

Avoid prop drilling across island boundaries through global window hacks.

Edge deployment

Astro on Cloudflare Pages deploys static HTML at edge; islands hydrate at edge or client depending on adapter. Verify client:visible Intersection Observer works when HTML served from CDN—no difference, but test lazy hydration with real mobile viewports.

Bundle analysis per island

Each island is separate JS entry—run analyzer per island chunk. React island importing entire @mui/material defeats purpose; import path-level or use lighter primitives.

client:visible vs client:load

Astro client:visible defers hydration until intersection — saves main thread on long pages. client:load for above-fold interactivity only. Misplaced client:load on footer newsletter defeats islands architecture.

SSR HTML must work without JS

Island architecture assumes static HTML is usable — search and nav work without hydration. Test with JS disabled before claiming partial hydration success.

Practical follow-through (1)

Ship the smallest vertical slice first — one route, one widget, one index configuration — with rollback documented before expanding scope. Baseline the user-visible metric this work protects (latency, recall, conversion, task success rate) for seven days before change and seven days after in your largest market.

Compare canary p75 to control before full rollout. Exercise edge paths manually: refresh, back navigation, double-submit, offline mode, and keyboard-only flows. When assumptions change — traffic doubles, vendor upgrades, org restructure — revisit whether the original design still fits; quiet periods hide drift until the next incident.

Practical follow-through (2)

Ship the smallest vertical slice first — one route, one widget, one index configuration — with rollback documented before expanding scope. Baseline the user-visible metric this work protects (latency, recall, conversion, task success rate) for seven days before change and seven days after in your largest market.

Compare canary p75 to control before full rollout. Exercise edge paths manually: refresh, back navigation, double-submit, offline mode, and keyboard-only flows. When assumptions change — traffic doubles, vendor upgrades, org restructure — revisit whether the original design still fits; quiet periods hide drift until the next incident.

Practical follow-through (3)

Ship the smallest vertical slice first — one route, one widget, one index configuration — with rollback documented before expanding scope. Baseline the user-visible metric this work protects (latency, recall, conversion, task success rate) for seven days before change and seven days after in your largest market.

Compare canary p75 to control before full rollout. Exercise edge paths manually: refresh, back navigation, double-submit, offline mode, and keyboard-only flows. When assumptions change — traffic doubles, vendor upgrades, org restructure — revisit whether the original design still fits; quiet periods hide drift until the next incident.

Practical follow-through (4)

Ship the smallest vertical slice first — one route, one widget, one index configuration — with rollback documented before expanding scope. Baseline the user-visible metric this work protects (latency, recall, conversion, task success rate) for seven days before change and seven days after in your largest market.

Compare canary p75 to control before full rollout. Exercise edge paths manually: refresh, back navigation, double-submit, offline mode, and keyboard-only flows. When assumptions change — traffic doubles, vendor upgrades, org restructure — revisit whether the original design still fits; quiet periods hide drift until the next incident.

Practical follow-through (5)

Ship the smallest vertical slice first — one route, one widget, one index configuration — with rollback documented before expanding scope. Baseline the user-visible metric this work protects (latency, recall, conversion, task success rate) for seven days before change and seven days after in your largest market.

Compare canary p75 to control before full rollout. Exercise edge paths manually: refresh, back navigation, double-submit, offline mode, and keyboard-only flows. When assumptions change — traffic doubles, vendor upgrades, org restructure — revisit whether the original design still fits; quiet periods hide drift until the next incident.

Resources

Frequently asked questions

What is the main production risk with web islands partial hydration?

Teams ship without field measurement—web islands partial hydration failures appear as silent UX regressions, cost drift, or audit findings rather than clear errors.

When should we prioritize web islands partial hydration?

Prioritize when user research, CrUX, support tickets, or compliance requirements show pain on critical paths—not when a checklist mentions it abstractly.

How do we validate web islands partial hydration changes?

Baseline RUM before changes, compare p75 after deploy, and keep rollback via feature flags or cache purge documented in the PR.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →