Skip to content
Astro as getting-started 4 min read

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

DirectiveWhen it hydratesBest for
client:loadImmediately on page loadHigh-priority, above-the-fold UI
client:idleWhen the browser is idle (requestIdleCallback)Lower-priority widgets
client:visibleWhen the island scrolls into viewBelow-the-fold components
client:media={query}When a CSS media query matchesMobile-only or responsive widgets
client:only={framework}Skips SSR; renders only on the clientComponents 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:visible or client:idle over client:load whenever 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 .astro components and only reach for a client:* directive when interactivity is truly required.
  • Use client:visible and client:idle to defer non-critical islands instead of client:load.
  • Reserve client:only for 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:defer for personalized or expensive content so the static shell can stay cached and fast.
Last updated June 14, 2026
Was this helpful?