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

DirectiveHydrates whenUse case
client:loadImmediately, on hydrationA control visible above the fold
client:idleAt the first idle momentA widget that must not block reading
client:visibleWhen it enters the viewportContent below the fold
client:mediaWhen the media query matchesMobile menu
client:onlyIn the browser onlyA 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:load on everything: the page costs like a single-page app again.
  • Using client:visible on 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.