Skip to content
C Codeloom
Astro

Astro Islands Architecture Explained

Learn how Astro ships zero JavaScript by default and only hydrates the interactive components you mark as islands.

·4 min read · By Codeloom
Beginner 8 min read

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
Static page with two islands

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.