Islands & Partial Hydration
islands & partial hydration islands and partial hydration deliver seo-friendly static html while attaching j islands architecture and partial hydration
Introduction
Islands architecture and partial hydration ship mostly static HTML and attach JavaScript only to interactive regions ("islands"). Astro popularized islands; React Server Components and Qwik push selective hydration further. The goal is the same: cut JS bytes and main-thread work on content-heavy pages without surrendering SEO-rich HTML.
Business problem
Business pressure: Marketing pages loaded 400KB of React to power one carousel and a newsletter form. LCP and INP suffered; ad spend ROI dropped. Islands let teams keep static HTML for 95% of the DOM and hydrate 5% — measurable CWV wins.
- Perf budget: Content sites target <100KB JS — full hydrate blows the budget.
- SEO: Static HTML body with island markers preserves crawlable content.
- DX: Teams want component ergonomics without shipping a root
createRooton every page.
Why this feature exists
Platform motivation: Full-document hydration duplicated server HTML work on the client — wasteful for blogs, docs, and landing pages. Frameworks introduced island boundaries so only components with client:* directives (or equivalent) ship JS.
- History: MPA static sites → SPA everything → islands/partial hydrate as correction.
- Alternative rejected: iframes for widgets — SEO, a11y, and styling pain.
- Modern role: Astro, Marko, Fresh, Qwik, Next client components — same architectural idea, different syntax.
Browser internals
Inside the engine: Server sends HTML with markers (e.g. data-astro-cid, q:slot). Parser builds full DOM. Island scripts load with async/type=module, hydrate only matching subtrees — rest stays inert HTML.
- Hydration mismatch: Server and client HTML must match inside island boundaries or React throws.
- Event delegation: Some frameworks defer listener attachment until island visible (Intersection Observer).
- Ordering: Islands must not block LCP element — defer non-critical island scripts.
Rendering workflow
Rendering path: SSG/SSR produces HTML string. Build step extracts island dependency graph. Browser paints static content; island bundles load in parallel; hydration runs per island, not full tree.
- Static shell: Immediate FCP/LCP from HTML/CSS.
- Island boot: Separate entry chunks — cache independently.
- Partial hydrate: Framework skips inert server component subtrees entirely on client.
Feature deep dive
Island boundaries — declare interactivity explicitly; default is static. Pass serializable props from server to island via attributes or embedded JSON with CSP-safe encoding.
- Directive:
client:load,client:visible,client:idle— trade TTI for bytes. - Props: Serialize to DOM — never pass functions from server HTML.
- Styling: Scoped CSS per island vs global design tokens — plan specificity.
<!-- Static HTML (no JS) --><article><h1>Launch recap</h1><p>Full article text for SEO and screen readers.</p></article><!-- Island: hydrates on visible --><div data-island="cart-button" data-props='{"sku":"ABC"}'><button type="button">Add to cart</button></div><script type="module" src="/islands/cart-button.js" async></script>
Accessibility analysis
A11y architecture: Static content is immediately available to assistive tech. Islands that replace static placeholders must preserve accessible names and not steal focus on hydrate.
- Buttons: Server HTML should use real
<button>— not div upgraded on hydrate. - Live regions: Island cart updates need
aria-liveafter enhancement loads.
SEO impact
SEO architecture: Islands excel on content sites — article body is pure HTML. Avoid island-only product titles or prices; crawlers must see them in source.
- View-source test: Primary keywords in static HTML, not injected post-hydrate.
- JSON-LD: Server-rendered in
<script type="application/ld+json">— outside islands.
Security considerations
Security boundary: Serialized props in HTML attributes are injection targets — use JSON encoding and CSP. Each island bundle is a supply-chain surface — subresource integrity where possible.
- Props XSS: Never interpolate user content into
data-propswithout encoding. - Island scripts: Scope permissions — cart island should not access admin APIs.
Performance impact
Performance: Islands directly improve INP and TBT by reducing hydrate scope. Choose client:visible for below-fold widgets.
- LCP: Do not hydrate hero — static image and text.
- INP: Fewer hydrated components = fewer main-thread listeners.
- Bytes: Track per-page island bundle sum in CI budget.
Real production example
Production pattern: Docs site on Astro: MDX content static; client:visible for code playground; client:load only for site search. RUM compares LCP before/after migrating from Next full hydrate.
- Metrics: 62% JS reduction; LCP −1.1s mobile p75.
- CI: Fail build if new island uses
client:loadwithout perf review.
Enterprise usage
Enterprise: Marketing within large org adopts islands while app team stays on SPA — shared design system ships CSS + web components as static-friendly islands.
- Platform: Approved island directives and prop serialization library.
- Governance: Architecture review when island count >5 per page.
Common production failures
What breaks in prod: Hydration mismatch on island after A/B test changed server HTML. Or client:load on every widget — recreated full-app hydrate problem.
- Incident: Double cart buttons — server HTML + client render both visible until CSS hack.
- Perf: 12 islands with
client:load— TBT regression; fixed with visible/idle directives.
Architecture review questions
- Which components truly need client directives on this page?
- Is primary content visible in view-source without executing JS?
- What hydration mismatch tests run in CI?
- How are island props serialized safely?
- What is total island JS weight vs budget?
Hands-on project
Project: Convert a static article page to islands: static article + one interactive poll (visible) + one search box (idle). Measure JS weight and Lighthouse performance.
- Deliverable: HTML output, island entries, before/after byte count.
Interview questions
Islands vs partial hydration vs RSC — how do you explain the trade-off?(Advanced)
All reduce client JS by keeping most markup server/static. Islands are explicit component boundaries with separate bundles; partial hydration skips inert subtrees; RSC sends server components without client reference. Choose based on framework ecosystem and team skill.
Follow-up: When would you still full-hydrate?
Try it yourself
Edit the HTML, CSS, or JS panels — the preview updates as you type.
Try it yourself
Summary
Islands and partial hydration deliver SEO-friendly static HTML while attaching JavaScript only where needed. Staff engineers enforce island budgets, safe prop serialization, and hydration tests to avoid recreating SPA bloat on content pages.