TL;DR / Islands architecture renders independent interactive components ("islands") with their own JavaScript, surrounded by a "sea" of static server-rendered HTML that ships no JS.
How It Works
Static HTML sea:
┌────────────┐ ╔════════════╗ ┌────────────┐
│ Static Nav │ ║ Carousel ║ │ Content │ - static (no JS)
└────────────┘ ╚════════════╝ └────────────┘
┌────────────┐ ╔════════════╗ ┌────────────┐
│ Sidebar │ ║ Comments ║ │ Footer │ = island (own JS)
└────────────┘ ╚════════════╝ └────────────┘
Islands hydrate independently
Islands architecture, a term coined by Katie Sylor-Miller and popularized by Jason Miller, is a component-level rendering strategy. The page is server-rendered as complete HTML. Within that HTML, specific interactive regions — the islands — are independently hydrated with their own isolated JavaScript bundles. Everything else remains as static HTML with zero client-side JavaScript cost.
How Islands Differ From Partial Hydration
Partial hydration and islands architecture are closely related, but islands add a crucial property: independence. Each island is a self-contained unit with its own rendering lifecycle. Island A can hydrate, re-render, and manage state without affecting Island B. There is no shared virtual DOM tree, no single root component, and no framework-level coordination between islands.
This independence means islands can even use different frameworks. You could have a React carousel island and a Svelte comment form island on the same page. Each island bootstraps its own runtime within its DOM boundary. This is impractical with traditional SPAs where the entire page shares one framework instance.
The Loading Strategy
Islands support granular loading strategies. Astro, the most prominent islands architecture framework, offers several hydration triggers:
client:load— hydrate immediately when the page loadsclient:idle— hydrate when the browser is idle (viarequestIdleCallback)client:visible— hydrate when the island scrolls into the viewport (viaIntersectionObserver)client:media— hydrate when a CSS media query matches (e.g., only on desktop)client:only— skip server rendering entirely, render only on the client
These triggers let you match hydration cost to user need. A carousel above the fold uses client:load. A comment section below the fold uses client:visible. A mobile menu uses client:media="(max-width: 768px)". Each decision removes unnecessary JavaScript execution from the critical rendering path.
How Islands Are Rendered
The server renders the complete page, including the initial HTML for each island. Islands are wrapped in a marker element (typically a custom element or a <div> with a data attribute). The island's props are serialized into the HTML — either as a <script type="application/json"> tag or as a data attribute.
When the island's JavaScript loads, it reads the serialized props, creates a component instance scoped to its DOM subtree, and hydrates. The rest of the page is never touched by JavaScript. The framework does not even parse the static DOM — it literally does not exist in the framework's world.
Performance Characteristics
Islands architecture provides predictable performance that scales with interactivity, not page size. Adding more static content (paragraphs, images, tables) has zero JavaScript cost. Only adding more islands increases the JavaScript budget. This makes it ideal for content-rich sites: documentation, blogs, marketing pages, e-commerce product pages, and news sites.
The trade-off is application-level state management. Since islands are isolated, sharing state between them requires external mechanisms: URL state, custom events, a shared store on window, or a pub/sub system. This is manageable for loosely coupled islands but becomes painful for highly interactive applications where many components need to react to the same state changes.
Islands vs. SPAs
Islands architecture is not a replacement for single-page applications. A complex dashboard with dozens of interconnected widgets, drag-and-drop, and real-time updates is better served by a traditional SPA. Islands shine when the page is primarily content with pockets of interactivity. The architecture forces you to explicitly identify what needs JavaScript, which is a healthy constraint for most websites.
Gotchas
- Inter-island communication is manual. There is no built-in state sharing between islands. You need custom events, URL params, or a shared global store.
- Each island carries its own framework runtime cost. Two React islands means the React runtime is included twice unless the build system deduplicates it. Astro handles this, but custom setups may not.
- Server rendering is mandatory. Islands assume the page is server-rendered. You cannot use islands architecture with a purely client-rendered SPA.
- Form state across islands is tricky. A form split across multiple islands (e.g., inputs in one, submit button in another) requires explicit coordination that a single component tree would handle automatically.
- ARIA relationships across islands require manual wiring. Islands render as independent DOM subtrees, so
aria-controls,aria-labelledby, and similar attributes that reference elements in other islands need explicit coordination.