---
title: "Astro: Islands Architecture"
slug: "astro-islands"
category: "frontend"
tags: ["frontend", "astro", "islands", "hydration", "performance"]
status: "stable"
last_updated: 2026-05-14
summary: "Partial hydration directives, choosing between client:load/idle/visible/media/only, and keeping your JS budget under control."
related: ["[[frontend/astro]]", "[[frontend/astro-performance]]", "[[frontend/astro-integrations]]", "[[comparisons/nextjs-vs-astro]]", "[[seo/page-speed]]", "[[frontend/react]]"]
---

## Overview

Astro's islands architecture ships zero JavaScript by default and lets you opt into client-side hydration per component. Each interactive component is an island: isolated, independently hydrated, surrounded by static HTML. This model keeps page weight small on content-heavy sites and gives you precise control over when the browser pays the JS parse and execute cost.

## Understand what an island actually is

An island is a framework component (React, Vue, Svelte, Solid, or Preact) that Astro server-renders to HTML and then selectively rehydrates on the client. Outside that island, the page is inert HTML. The browser downloads the island's framework runtime and component bundle only when the directive fires, not on page load.

Without a `client:*` directive, the component renders to HTML once at build time (or request time for SSR routes) and ships no JS. That is the correct default for decorative or display-only components.

## Use `client:load` for above-the-fold interactivity

`client:load` hydrates the component immediately when the page loads. Use it for interactive elements the user will touch within the first second: a theme toggle, a sticky search bar, an auth button. Avoid it for anything below the fold; every `client:load` island adds to the blocking parse cost.

```astro
<ThemeToggle client:load />
```

## Use `client:idle` for non-critical widgets

`client:idle` waits for `requestIdleCallback` before hydrating. The browser hydrates these islands after layout-critical work is done. Use it for elements the user may interact with but does not need instantly: a newsletter form in a sidebar, a "share" button, a non-critical tooltip component.

```astro
<NewsletterForm client:idle />
```

## Use `client:visible` as the default for below-the-fold islands

`client:visible` fires an `IntersectionObserver` and hydrates when the component scrolls into the viewport. This is the right default for most islands below the fold: comment sections, embedded charts, media players, interactive demos. The user gets the island hydrated just before they need it.

```astro
<CommentsSection client:visible />
```

## Use `client:media` for responsive interactive regions

`client:media` accepts a CSS media query and hydrates only when that query matches. Use it for components that only make sense at certain viewport sizes: a mobile drawer nav that does not exist on desktop, a swipe carousel for narrow screens.

```astro
<MobileMenu client:media="(max-width: 768px)" />
```

## Use `client:only` when server rendering would break the component

`client:only` skips server rendering entirely and runs the component only on the client. Use it for components that depend on browser APIs: a chart library that reads `window.innerWidth` on mount, a canvas drag interface, a WebGL renderer. Pass the framework string explicitly.

```astro
<DragCanvas client:only="react" />
```

Do not use `client:only` as a workaround for hydration errors in components that could server-render correctly. Fix the component; `client:only` costs a full layout flash.

## Keep a per-page JS budget

Track how much JS each island adds. Run `astro build` and inspect the generated `dist/` bundles. Each framework runtime ships once per framework per page: React adds roughly 45 KB (gzipped) the first time a React island appears. A second React island on the same page reuses that runtime; it is not doubled.

Rules for the budget:

- One framework per page when possible. Mixing React and Svelte islands on the same page ships two runtimes.
- Prefer `client:visible` and `client:idle` over `client:load` to delay runtime parse time.
- Shared state between islands belongs in a store like Nano Stores, not in React context. Islands are isolated; React context does not cross island boundaries.
- Audit with [[frontend/astro-performance]] rules before shipping a page past 50 KB JS.

## Related

- [[frontend/astro]]
- [[frontend/astro-performance]]
- [[frontend/astro-integrations]]
- [[comparisons/nextjs-vs-astro]]
- [[seo/page-speed]]
- [[frontend/react]]
