Artistry & Intelligence

Enter the Atelier

Technical Reference

React Islands Architecture

How Astro's islands architecture enables selective hydration for optimal performance.

What are islands

Islands are independent, interactive components that load JavaScript only where needed. The rest of the page remains static HTML, reducing bundle size and improving performance.

Key concepts

  • Static by default - Astro components render to HTML with zero JavaScript
  • Selective hydration - Only interactive components load client JavaScript
  • Framework agnostic - Use React, Vue, Svelte, or mix frameworks
  • Partial hydration - Each island hydrates independently

Client directives

Client directives control when and how React components hydrate on the client.

client:load High Priority

Hydrate immediately on page load. Use for above-the-fold interactive elements critical to initial page experience.

---
import ChatInterface from '@/components/ChatInterface';
---

<ChatInterface client:load />

When to use: Critical interactive UI, chat interfaces, real-time features

client:idle Medium Priority

Hydrate when browser is idle (uses requestIdleCallback). Good default for most interactive components.

---
import SearchWidget from '@/components/SearchWidget';
---

<SearchWidget client:idle />

When to use: Forms, search, navigation menus, secondary features

client:visible Low Priority

Hydrate when component enters viewport (uses IntersectionObserver). Ideal for below-the-fold content.

---
import ImageCarousel from '@/components/ImageCarousel';
---

<ImageCarousel client:visible />

When to use: Carousels, charts, animations below fold, lazy-loaded widgets

client:media Conditional

Hydrate when CSS media query matches. Perfect for responsive components that only work on specific screen sizes.

---
import MobileMenu from '@/components/MobileMenu';
import DesktopMenu from '@/components/DesktopMenu';
---

<MobileMenu client:media="(max-width: 768px)" />
<DesktopMenu client:media="(min-width: 769px)" />

When to use: Mobile-only or desktop-only features

client:only="react" Client Only

Skip server rendering entirely. Component only renders on client. Use when server rendering causes issues.

---
import BrowserOnlyWidget from '@/components/BrowserOnlyWidget';
---

<BrowserOnlyWidget client:only="react" />

When to use: Components that depend on browser APIs, third-party widgets, experimental features

Directive decision matrix

Choose the right directive based on your component's purpose and location.

If your component... Use Reason
Needs immediate interaction client:load Critical path, above fold
Is a form or search client:idle Important but not critical
Is below the fold client:visible Lazy load for performance
Only works on mobile/desktop client:media Avoid loading unused code
Breaks during SSR client:only Skip server rendering

Creating React islands

Build React components optimized for island architecture.

Basic React island

Counter.tsx
// src/components/Counter.tsx
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div className="p-4 border rounded-lg">
      <p className="mb-2">Count: {count}</p>
      <button
        onClick={() => setCount(count + 1)}
        className="px-4 py-2 bg-craft-accent text-white rounded"
      >
        Increment
      </button>
    </div>
  );
}

Using the island in Astro

---
// src/pages/example.astro
import Counter from '@/components/Counter';
---

<html>
  <body>
    <h1>Static Header</h1>
    <p>Static paragraph with no JavaScript</p>

    <!-- Only this component hydrates -->
    <Counter client:idle />

    <footer>Static footer</footer>
  </body>
</html>

Island with props

Greeting.tsx
// src/components/Greeting.tsx
interface GreetingProps {
  name: string;
  initialCount?: number;
}

export default function Greeting({ name, initialCount = 0 }: GreetingProps) {
  const [count, setCount] = useState(initialCount);

  return (
    <div>
      <p>Hello, {name}!</p>
      <p>Visits: {count}</p>
      <button onClick={() => setCount(count + 1)}>Visit Again</button>
    </div>
  );
}

// In Astro:
// <Greeting client:load name="Ryan" initialCount={5} />

State management patterns

Islands are isolated by default. Share state between islands using these patterns.

Local storage (simple)

// src/components/ThemeToggle.tsx
import { useState, useEffect } from 'react';

export default function ThemeToggle() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  useEffect(() => {
    const saved = localStorage.getItem('theme');
    if (saved) setTheme(saved as 'light' | 'dark');
  }, []);

  const toggle = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.classList.toggle('dark');
  };

  return <button onClick={toggle}>Toggle Theme</button>;
}

Nano Stores (recommended)

// src/stores/user.ts
import { atom } from 'nanostores';

export const userStore = atom({ name: '', loggedIn: false });

// In any React island:
import { useStore } from '@nanostores/react';
import { userStore } from '@/stores/user';

export default function UserGreeting() {
  const user = useStore(userStore);

  return <p>Welcome, {user.name}!</p>;
}

Custom events (cross-framework)

// Dispatch from one island
const event = new CustomEvent('user-login', {
  detail: { userId: 123 }
});
window.dispatchEvent(event);

// Listen in another island
useEffect(() => {
  const handler = (e: CustomEvent) => {
    console.log('User logged in:', e.detail.userId);
  };

  window.addEventListener('user-login', handler as EventListener);
  return () => window.removeEventListener('user-login', handler as EventListener);
}, []);

Performance considerations

Optimize island performance with these best practices.

Minimize island count

Each island adds overhead. Combine related interactive elements into single islands.

<!-- Instead of multiple islands: -->
<LikeButton client:idle />
<ShareButton client:idle />
<CommentButton client:idle />

<!-- Combine into one: -->
<SocialActions client:idle />

Choose appropriate directives

Use client:visible for below-the-fold content. Avoid client:load unless critical.

Code splitting

Heavy dependencies should be dynamically imported.

import { useState } from 'react';

export default function ChartWidget() {
  const [Chart, setChart] = useState(null);

  useEffect(() => {
    // Load chart library only when component mounts
    import('chart.js').then((module) => setChart(module.default));
  }, []);

  return Chart ? <Chart data={data} /> : <p>Loading...</p>;
}

Avoid prop drilling

Use Nano Stores or Context for deeply nested state instead of passing props through multiple levels.

Debugging islands

Diagnose island hydration and rendering issues.

Check hydration in DevTools

// Add data-astro-cid attribute to identify islands
<div data-island="ChatInterface">
  <!-- Island content -->
</div>

// Check console for hydration errors
// Look for React warnings about mismatched HTML

Common issues

Issue Solution
Island doesn't hydrate Check client: directive is present
Hydration mismatch error Ensure server and client HTML match exactly
Props not passing correctly Verify props are serializable (no functions)
Window/document undefined Use useEffect or client:only

Enable Astro Dev Toolbar

// astro.config.mjs
export default defineConfig({
  devToolbar: { enabled: true }
});

// View island boundaries and hydration status in browser

Best practices summary

  • Use client:idle as default for most interactive components
  • Prefer client:visible for below-the-fold content
  • Reserve client:load for critical, above-the-fold interactions
  • Combine related interactive elements into single islands
  • Use Nano Stores for shared state across islands
  • Dynamically import heavy dependencies
  • Test hydration in production builds, not just dev mode
  • Avoid window or document in component body (use useEffect)

See also in Craft

Component Library

Catalog of all available components

See also in Experience

Advanced Techniques Tutorial

Performance optimization and custom components

Loading Johnny Castaway...

Original Johnny Castaway (1992) by Sierra On-Line/Dynamix
Recreation by xesf on GitHub
Click anywhere or press ESC to exit
Click anywhere or press ESC to exit
Recreation by Bryan Braun
Original Berkeley Systems After Dark (1989)
Click anywhere or press ESC to exit
supported.systems
MISSION CONTROL
CURRENT RELEASE v6.0.0-beta.18
LAUNCH DATE Mar 4, 2026
RECENT UPDATES
  • #15668 `1118ac4` Thanks @florian-lefebvre! - Changes Ty...
  • #15726 `6f19ecc` Thanks @ocavue! - Updates dependency `...
  • #15694 `66449c9` Thanks @matthewp! - Adds `preserveBuil...
All systems nominal
Click anywhere or press ESC to exit
The web framework for content-driven websites