Astro Islands
TL;DR: Astro ships almost no JavaScript by default. Instead of hydrating the whole app, it hydrates islands — small interactive pieces on otherwise static pages. Result: faster load, less JS, happier users.
What’s an “island” anyway?
Imagine your site is 95% static (headers, articles, footers, etc).
But you have a few components that actually need interactivity: a counter, a form, maybe a chart.
Astro says:
👉 render everything as static HTML on the server,
👉 then sprinkle JS only on the interactive components.
index.astro
├── <Header /> → HTML only
├── <Article /> → HTML only
└── <Counter /> → hydrated client-side
A tiny m-demo
Let’s build the simplest interactive island: a counter.
/src/components/Counter.jsx
import { useState } from "react";
export default function Counter() {
const [n, set] = useState(0);
return (
<button
onClick={() => set(n + 1)}
className="px-3 py-1 rounded border"
>
Count: {n}
</button>
);
}
Now drop it into an Astro page:
/src/pages/islands.astro
---
import Counter from "../components/Counter.jsx";
---
<section class="card p-6">
<h1>Astro Islands</h1>
<!-- Different hydration strategies -->
<Counter client:load /> <!-- hydrate immediately -->
<Counter client:idle /> <!-- hydrate when browser is idle -->
<Counter client:visible /> <!-- hydrate when visible in viewport -->
<Counter client:media="(min-width: 800px)" /> <!-- hydrate conditionally -->
<Counter client:only="react" /> <!-- render as a pure React island -->
</section>
Hydration strategies: which one when?
client:load
→ must be interactive ASAP (menus, forms)client:idle
→ low-priority stuff (animations, small widgets)client:visible
→ heavy components below the fold (charts, maps)client:media
→ hydrate only on certain breakpointsclient:only
→ whole page is just one React/Vue/Svelte island
It’s like having defer
/lazy
/conditional
but built into your component syntax.
Multiple islands, independent lifecycles
Each island has its own bundle and lifecycle.
3 different widgets = 3 isolated React bundles, no shared hydration overhead.
This means you don’t “pay” for one giant React app, you just hydrate what’s needed.
Progressive enhancement by default
Astro always ships fallback HTML first.
---
import Views from "../components/Views.jsx";
---
<p>
Views: <span>…</span>
<Views client:visible />
</p>
Without JS, users see …
.
When JS kicks in, <Views />
replaces it.
Accessible and safe.
Measuring the win
Run Lighthouse on a page full of islands vs. the same page as a full SPA:
- Smaller JS bundles (kB instead of MB).
- Faster Time to Interactive (TTI).
- Better Core Web Vitals scores.
Real world: marketing pages, blogs, docs, portfolios - they often need only sprinkles of JS. Astro is perfect.
Islands aren’t just a buzzword. They’re a new mental model: ship less, hydrate smartly.
Once you play with client:visible
or client:idle
, it feels obvious — why should a blog page load 200kB of React when only a like button needs JS?
Astro is teaching us: static by default, interactive only where it matters.