Skip to content

When to Use an Island

The central Astro philosophy is simple: start static, opt into JS only when you need it. This is the opposite of a React SPA where everything is JavaScript until you work to remove it.

Ask this question for every component on your page:

“Does this component respond to user interaction or change over time in the browser?”

If yes — make it an island with the right client:* directive. If no — keep it as a .astro component or a React component without a directive (rendered to static HTML).

React
// In a React/Next.js app everything is a component.
// Making something "static" requires extra effort.
export default function BlogPage({ post, related }) {
return (
<div>
{/* These are all in the React tree, all hydrated */}
<Header /> {/* purely presentational */}
<ArticleContent post={post} /> {/* purely presentational */}
<LikeButton postId={post.id} /> {/* interactive */}
<ShareMenu url={post.url} /> {/* interactive */}
<RelatedPosts posts={related} />{/* purely presentational */}
</div>
);
}
Astro
---
// In Astro, ask "does this need JS?" for each piece.
import Header from '../components/Header.astro'; // no JS
import ArticleContent from '../components/ArticleContent.astro'; // no JS
import LikeButton from '../components/LikeButton.jsx'; // needs JS
import ShareMenu from '../components/ShareMenu.jsx'; // needs JS
import RelatedPosts from '../components/RelatedPosts.astro'; // no JS
const { post, related } = Astro.props;
---
<Header />
<ArticleContent post={post} />
<LikeButton client:visible postId={post.id} />
<ShareMenu client:idle url={post.url} />
<RelatedPosts posts={related} />

On a typical blog post page, two islands (LikeButton and ShareMenu) ship JavaScript. Everything else is zero-JS HTML.

This is the most important architectural difference from a React SPA. In a React app there is one component tree. React Context flows from a top-level Provider down to any descendant, no matter how deep.

In Astro, each island is a separate React root. Context does not cross island boundaries.

// This does NOT work across islands:
//
// .astro page:
// <ThemeProvider client:load> ← island A
// <ThemedButton client:load /> ← island B (separate root!)
//
// ThemeProvider's context is not visible to ThemedButton.

Option 1 — Wrap both in a single island:

---
import ThemedApp from '../components/ThemedApp.jsx';
---
<ThemedApp client:load />
// ThemedApp.jsx — one island, one React root, context works
import { ThemeProvider } from './ThemeContext';
import ThemedButton from './ThemedButton';
export default function ThemedApp() {
return (
<ThemeProvider>
<ThemedButton />
</ThemeProvider>
);
}

Option 2 — Use a shared signal/store outside React:

Libraries like nanostores (Astro’s recommended tiny store) let islands share state without a common React root:

src/stores/theme.js
import { atom } from 'nanostores';
export const theme = atom('light');
// island A
import { useStore } from '@nanostores/react';
import { theme } from '../stores/theme';
export default function ThemeToggle() {
const t = useStore(theme);
return <button onClick={() => theme.set(t === 'light' ? 'dark' : 'light')}>{t}</button>;
}
// island B — reads the same store
import { useStore } from '@nanostores/react';
import { theme } from '../stores/theme';
export default function ThemedButton() {
const t = useStore(theme);
return <button class={t}>Click me</button>;
}
ComponentNeeds hydration?What to use
Navigation headerNo (links only).astro component
Hero imageNo<img> in .astro
Article bodyNo.astro component
Like / upvote buttonYes — click handlerReact island client:visible
Comments sectionYes — fetch + renderReact island client:visible
Mobile menu toggleYes — open/close stateReact island client:media
Page layoutNo.astro layout
Date formatterNo (build-time).astro or static React
A navigation bar that only renders links — no dropdowns, no state. What should it be in Astro?
Two separate React islands on the same Astro page need to share theme state. What is the correct approach?
Why does React Context NOT work across two separate islands by default?
What is the recommended Astro-native solution for sharing reactive state between multiple islands?