React Islands

How to use React components inside Astro, when to keep them, and when to convert to .astro.

Setup

npx astro add react

This installs @astrojs/react, react, and react-dom.

Using React components

Import and use React components in .astro files:

---
import Counter from '../components/Counter.jsx';
---
<!-- Static: renders to HTML, no JS shipped -->
<Counter />

<!-- Interactive: hydrates in the browser -->
<Counter client:load />

Without a client:* directive, React components render once on the server and become static HTML. With a directive, they hydrate and become interactive.

Hydration strategies

DirectiveLoads whenBest for
client:loadPage loadCritical interactive elements
client:idleBrowser idleNon-critical interactivity
client:visibleEnters viewportBelow-fold content
client:media="(max-width: 768px)"Media query matchesResponsive interactivity
client:only="react"Client only, no SSRBrowser-only components

Passing props and children

Props work as expected:

<SearchBar client:load placeholder="Search articles..." onResults={undefined} />

Children use Astro’s slot mechanism:

<ReactWrapper client:load>
  <p>This HTML becomes children in React</p>
</ReactWrapper>

Named slots map to React props:

<ReactCard client:load>
  <h2 slot="header">Title</h2>
  <p>Body content</p>
</ReactCard>

In the React component, header arrives as props.header and default content as props.children.

When to convert vs keep React

Convert to .astro when the component:

  • Has no interactivity (no useState, useEffect, event handlers)
  • Only displays data
  • Is a layout or structural wrapper

Keep as React when the component:

  • Needs client-side state or effects
  • Handles user input
  • Uses React-specific libraries (React Query, Framer Motion, etc.)
  • Would be expensive to rewrite for little benefit

Rule of thumb: if removing hooks leaves you with a function that returns JSX with no event handlers, it should be an Astro component.

Example: converting a static React component

Before (React):

export default function ArticleCard({ title, date, excerpt }) {
  return (
    <article className="card">
      <h2>{title}</h2>
      <time>{new Date(date).toLocaleDateString()}</time>
      <p>{excerpt}</p>
    </article>
  );
}

After (Astro):

---
interface Props {
  title: string;
  date: string;
  excerpt: string;
}
const { title, date, excerpt } = Astro.props;
---
<article class="card">
  <h2>{title}</h2>
  <time>{new Date(date).toLocaleDateString()}</time>
  <p>{excerpt}</p>
</article>

<style>
  .card { /* scoped styles */ }
</style>

Benefits: zero JS shipped, scoped styles, simpler code.

Sharing state between islands

Islands are isolated. Use Nano Stores for cross-island state:

npm install nanostores @nanostores/react
// src/stores/search.js
import { atom } from 'nanostores';
export const searchQuery = atom('');
// SearchInput.jsx
import { useStore } from '@nanostores/react';
import { searchQuery } from '../stores/search';

export default function SearchInput() {
  const $query = useStore(searchQuery);
  return <input value={$query} onChange={e => searchQuery.set(e.target.value)} />;
}
// SearchResults.jsx
import { useStore } from '@nanostores/react';
import { searchQuery } from '../stores/search';

export default function SearchResults() {
  const $query = useStore(searchQuery);
  // filter/display results based on $query
}

Both islands update reactively from the same store, despite being separate React trees.

Nested Astro inside React? No.

React components cannot import Astro components. The nesting goes one way:

  • Astro can render React components (with or without hydration)
  • React components can only import other React (or JS) components

If you need to compose Astro content inside a React island, pass it as children via slots.

Performance considerations

Each client:* island loads:

  • The React runtime (~40KB gzipped for React 19)
  • The component code
  • Any dependencies

Multiple React islands on the same page share a single React runtime, but each hydrates independently. If you have many small islands, consider whether a single larger island or a vanilla <script> would be more efficient.