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 breakpoints
  • client: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.