Islands Architecture
Islands architecture is the idea that defines Astro. Instead of shipping a single large JavaScript bundle that hydrates the whole page, Astro renders almost everything to static HTML at build time and lets you opt small, isolated interactive components into hydration on a case-by-case basis. The result is pages that are HTML-first by default, with JavaScript loaded only where genuine interactivity is needed. This is why Astro sites tend to be dramatically faster than equivalent single-page applications.
What is an island?
An “island” is an interactive UI component rendered on an otherwise static HTML page. Picture a page as a calm sea of static content — headings, paragraphs, images, navigation — dotted with a few interactive islands such as a search box, an image carousel, or a “buy now” widget. Each island is hydrated independently and in isolation; the static ocean around it never ships JavaScript.
The term was popularized by Etsy’s Katie Sylor-Miller and Preact’s Jason Miller. Astro was the first major framework to make it the default model rather than an opt-in optimization.
Two properties make islands powerful:
- Isolation — each island hydrates on its own. A heavy chart widget loading does not block a small newsletter form from becoming interactive.
- Independence — islands can be written in different UI frameworks on the same page. A React island and a Svelte island can coexist.
Zero JavaScript by default
Every Astro component (.astro file) renders to pure HTML with no client-side runtime. Consider a component that contains a framework component but no client directive:
---
// src/pages/index.astro
import Counter from '../components/Counter.jsx';
---
<html lang="en">
<body>
<h1>Welcome</h1>
<!-- Rendered to static HTML, ships zero JS -->
<Counter start={0} />
</body>
</html>
The Counter above is server-rendered to HTML and is not interactive — clicking it does nothing because no JavaScript was shipped. That is the default, and for most of a typical page it is exactly what you want.
Creating an interactive island
To turn a component into a hydrated island, add a client:* directive. This tells Astro to ship the component’s JavaScript and hydrate it in the browser.
---
import Counter from '../components/Counter.jsx';
---
<!-- Now an interactive island: ships and hydrates JS -->
<Counter start={0} client:load />
The directive you choose controls when hydration happens, which directly affects performance.
Client directives
| Directive | When it hydrates | Best for |
|---|---|---|
client:load | Immediately on page load | High-priority, above-the-fold UI |
client:idle | When the browser is idle (requestIdleCallback) | Lower-priority widgets |
client:visible | When the island scrolls into view | Below-the-fold components |
client:media={query} | When a CSS media query matches | Mobile-only or responsive widgets |
client:only={framework} | Skips SSR; renders only on the client | Components that depend on browser-only APIs |
---
import Carousel from '../components/Carousel.svelte';
import Comments from '../components/Comments.jsx';
---
<!-- Defers JS until the carousel is scrolled into view -->
<Carousel client:visible />
<!-- Client-only React island; must name the framework -->
<Comments client:only="react" />
Tip: Prefer
client:visibleorclient:idleoverclient:loadwhenever you can. Deferring hydration keeps the page interactive sooner and reduces the JavaScript competing for the main thread on first paint.
Mixing frameworks on one page
Because islands are independent, you can use multiple UI frameworks together — handy when adopting Astro incrementally or sharing components across teams. Add the relevant integrations first:
npx astro add react svelte
Then use components from each side by side:
---
import ReactWidget from '../components/ReactWidget.jsx';
import SvelteWidget from '../components/SvelteWidget.svelte';
---
<ReactWidget client:load />
<SvelteWidget client:visible />
Each island bundles only its own framework runtime, and only when that island is actually hydrated.
Server islands
Modern Astro (4.x and 5.x) extends the idea to the server with the server:defer directive. The page returns instantly with placeholder HTML, and the deferred component is rendered on the server and streamed in afterward — ideal for personalized or slow-to-compute content that should not block the cached static shell.
---
import Avatar from '../components/Avatar.astro';
---
<!-- Static shell ships immediately; Avatar streams in after -->
<Avatar server:defer>
<div slot="fallback">Loading profile…</div>
</Avatar>
Why it matters
The architectural payoff shows up in real metrics. Because most of the page is static HTML, the browser has very little to download, parse, and execute before the page is usable.
Output:
Traditional SPA: ~300 KB JS parsed before any content is interactive
Astro islands: ~0 KB JS for static content + small per-island bundles
Less JavaScript on the main thread means faster Time to Interactive, better Core Web Vitals, and lower CPU cost on low-end devices.
Best practices
- Keep islands small and focused — hydrate the interactive widget, not its entire surrounding layout.
- Default to static
.astrocomponents and only reach for aclient:*directive when interactivity is truly required. - Use
client:visibleandclient:idleto defer non-critical islands instead ofclient:load. - Reserve
client:onlyfor components that genuinely cannot server-render, and always name the framework. - Pass data into islands as props rather than fetching inside every island; this keeps client bundles lean.
- Use
server:deferfor personalized or expensive content so the static shell can stay cached and fast.