Astro Islands Architecture Explained
Learn how Astro ships zero JavaScript by default and only hydrates the interactive components you mark as islands.
What you'll learn
- ✓What an island is in Astro
- ✓How client directives control hydration
- ✓Mixing React, Vue, and Svelte components
- ✓When to use client:load vs client:visible
- ✓Sharing state between islands
Prerequisites
- •Comfortable with HTML and JavaScript
What and Why
Most modern frameworks ship JavaScript for the entire page even when only a small widget needs interactivity. Astro flips that default. By default a page is HTML and CSS with no JavaScript at all. You opt in to hydration for specific components, and only those components become islands of interactivity in an otherwise static sea.
Mental Model
Imagine a beach photo. The sand is the static HTML. The few islands that poke above the surface are interactive components shipped with the JavaScript they need to run. Each island is independent. It has its own bundle, hydrates on its own schedule, and does not block the rest of the page.
HTML: <Header /> <Article /> <Comments /> <Footer />
island island
JS: 6 KB JS: 12 KB
the rest of the page ships zero JS
Hands-on Example
A typical Astro page mixes static components and islands.
---
// src/pages/index.astro
import Header from '../components/Header.astro';
import LikeButton from '../components/LikeButton.jsx';
import CommentBox from '../components/CommentBox.svelte';
---
<html>
<body>
<Header title="Welcome" />
<article>
<h1>Astro Islands</h1>
<p>Static prose ships as plain HTML.</p>
</article>
<LikeButton client:load initial={0} />
<CommentBox client:visible postId="42" />
</body>
</html>
Header.astro runs at build time and emits HTML. The React LikeButton hydrates immediately because it must respond to a click as soon as the page loads. The Svelte CommentBox waits until it scrolls into view, saving bandwidth for users who never scroll that far.
The client directives are the levers.
<Widget client:load /> // hydrate immediately
<Widget client:idle /> // hydrate during browser idle time
<Widget client:visible /> // hydrate when the element scrolls into view
<Widget client:media="(min-width: 768px)" /> // only on matching viewport
<Widget client:only="react" /> // skip server render, mount only on client
Each directive maps to a tiny runtime that defers loading the framework code until it is needed. The framework code itself, React or Svelte or Vue, is only shipped if at least one island uses it.
Common Pitfalls
People sometimes mark every component as client:load because that feels safest. This recreates the all-or-nothing hydration cost that islands are supposed to avoid. Start with no directive, then add the lightest one that works for the user experience.
Props passed to islands must be JSON serializable. Functions and class instances do not survive the trip from server to client. If you need to pass a callback, define it inside the client component or import it from a shared module that runs on both sides.
Sharing state between islands is not automatic. Each island is its own root. Use nanostores or another tiny global store if two islands must coordinate.
// src/stores/cart.js
import { atom } from 'nanostores';
export const cartCount = atom(0);
import { useStore } from '@nanostores/react';
import { cartCount } from '../stores/cart';
export default function CartBadge() {
const count = useStore(cartCount);
return <span>{count}</span>;
}
Practical Tips
Audit the network panel after a build. You should see almost no JavaScript on static pages. If a framework runtime appears unexpectedly, an island somewhere is loading it.
Mix frameworks deliberately, not by accident. Each framework you adopt adds a runtime. A small project can stick to one framework or even use Astro components for everything.
For above-the-fold widgets like a navigation toggle, client:load is correct. For below-the-fold widgets like a comment thread, client:visible is almost always better. client:idle is a middle ground for things that should appear quickly but are not urgent.
client:only is your escape hatch when a component depends on browser-only APIs like window or IntersectionObserver. It skips the server render entirely so you do not hit a hydration mismatch.
Wrap-up
The islands architecture is a principled answer to the question of how much JavaScript a content site really needs. Astro lets you ship interactivity exactly where you want it, in the framework you prefer, without paying for hydration everywhere else. Start static, add islands sparingly, and pick the lightest client directive that still feels good to use.
Related articles
- Astro Astro Image Component and Optimization
Master Astro's built-in Image and Picture components to ship responsive, lazy-loaded, modern-format images without external services.
- Astro Astro Middleware Tutorial
Use Astro middleware to run code on every request: auth gates, locals injection, redirects, and response headers. Learn the onRequest signature, the next() flow, sequencing, and common production patterns.
- Astro Astro Server Endpoints Tutorial
Build JSON APIs and dynamic responses directly in Astro using server endpoints. Learn the file conventions, request and response shapes, dynamic params, and how to mix endpoints with static and SSR pages.
- Astro Astro vs Next.js Comparison
Compare Astro and Next.js across rendering models, performance defaults, ecosystem, and use cases to pick the right framework for your project.