React
Interactive islands in Astro: when the JavaScript starts
An Astro page starts with no JavaScript: every component with a client directive is an island that hydrates alone, with its own bundle and cost.
Key points
- An .astro file produces HTML and CSS; JavaScript only arrives from an island with a client directive.
- Client directives work on framework components, never on .astro components.
- Two islands share no state: a shared value needs an external store.
In Astro a component ships JavaScript only when you say so: an .astro file produces HTML and CSS, and interactivity comes from an island with a client: directive. The static page stays static even when it holds an interactive widget.
The directive decides the moment
| Directive | Hydrates when | Use case |
|---|---|---|
| client:load | Immediately, on hydration | A control visible above the fold |
| client:idle | At the first idle moment | A widget that must not block reading |
| client:visible | When it enters the viewport | Content below the fold |
| client:media | When the media query matches | Mobile menu |
| client:only | In the browser only | A component that cannot render on the server |
npx astro add react
The integration adds the renderer: without it Astro cannot compile a React, Vue or Svelte component. Each framework has its own command and you can have several on one page, at the cost of one runtime per framework.
---
import Counter from "../components/Counter.jsx";
import Reviews from "../components/Reviews.jsx";
---
Props cross the boundary towards the browser and must be serialisable: a function or a class never reaches the island. The client: directives work only on framework components, never on .astro files.
One island, one bundle
Every island hydrates on its own: two islands do not share state just because they sit on the same page. A shared counter needs an external store such as nanostores, subscribed by both islands.
// src/stores/cart.js
import { atom } from "nanostores";
export const cartCount = atom(0);
// in ogni isola
// import { useStore } from "@nanostores/react";
Static content and server islands
Prose stays HTML: do not turn a whole page into a single island for convenience. Astro 5 adds server islands through server:defer, which render content on the server and hydrate only the interactive shell.
The limitation to plan for
An island pays for its framework runtime: two islands of the same framework share the code, but each has its own component and its own state. With client:only the component is not rendered on the server, so it is missing from the initial HTML and carries no content for crawlers.
Mistakes you see
- Putting
client:loadon everything: the page costs like a single-page app again. - Using
client:visibleon an element that is always at the top: you save nothing and the content jumps. - Expecting two islands to share state: each one has its own instance.
- Passing a callback as a prop to an island: the function does not cross the boundary.
Islands are filled with ready-made components: saastro and Astro Association install as source, and the hero block stays static until you add a directive. The missing directive is described in the missing client directive, the comparison with Next.js in Astro vs Next.js, routing in another framework in routing and layouts in SvelteKit.
Frequently asked questions
When do I need client:load?
When the component is visible immediately and must respond to the first click. For everything else start from idle or visible: loading at once cancels the Astro advantage.
Can islands share state?
Not on their own. Each island is a separate instance: sharing a value needs an external store such as nanostores, subscribed by both.
Does Astro support Vue and Svelte together?
Yes. Each integration adds a renderer and islands can use different frameworks on the same page. The cost is one runtime per framework.
What is client:only for?
It is for a component that cannot render on the server, for instance because it reads window straight away. In exchange you lose the initial HTML for that block.
Is content inside an island indexable?
Yes, when the island is rendered on the server: client:idle and client:visible leave the HTML in the document. With client:only the markup only arrives from the browser.