Astro Islands
By Syncfusion®, Inc. · Last updated
The islands architecture, adding a UI framework with npx astro add, framework components without a directive, client:load, client:idle, client:visible, client:media and client:only, props and children, sharing state between islands with a store, and when to use a script instead.
Astro Islands
- Most of a page is just reading: text, pictures, links.
- A few parts must react to clicks: a like button, a search box, a cart.
- Astro sends zero JavaScript by default.
- An island is one interactive component on an otherwise static page.
- In this lesson, you will add islands built with a UI framework, and choose when each one wakes up.
Simple lesson: an island is a small interactive part in a sea of plain HTML.
A Real-World Example
- Asha's post page is plain HTML. It loads fast.
- She wants one Like button that counts clicks.
- She does not want to ship JavaScript for the whole page.
- So she writes the button as a Preact component and adds
client:loadto it. Only the button gets JavaScript.
Press ▶ Run. Click the button. Then read the Terminal: only the button became interactive.
What happened:
- Astro rendered the button to HTML on the server, like everything else.
client:loadtold Astro: "also send this component's JavaScript".- In the browser, Preact hydrated the button: it attached the click code to the HTML that was already there.
What You Will Learn
In this lesson, you will learn:
- What the islands architecture is.
- How to add a UI framework such as Preact, React, Vue or Svelte.
- Why a framework component without a directive sends no JavaScript.
- The five
client:*directives and when to use each. - How to pass props and children to an island.
- How islands share state.
- When to use an island and when a plain
<script>is enough.
The Islands Architecture
Islands in a Sea of HTML
Animation: a page is mostly static HTML. Two small islands, a like button and a search box, light up one after the other as their JavaScript loads.
The page: static HTML (no JavaScript)
Header and navigation
Blog post text and images
Island: Like button
Island: Search box
Blue is plain HTML. Each orange island loads its own small JavaScript, on its own schedule.
- The page is the "sea": plain HTML and CSS.
- Each interactive component is an "island".
- Every island loads on its own. A slow island does not block the others.
- Many frameworks render the whole page in JavaScript. Astro hydrates only the islands.
Why Islands Are Fast
- Less JavaScript to download.
- Less JavaScript to run on a cheap phone.
- The text is visible before any JavaScript runs.
- You choose when each island loads: now, later, or only when it scrolls into view.
Astro components (.astro files) never run in the browser. Islands are built with a UI framework component, such as Preact, React, Vue, Svelte or Solid.
Simple lesson: islands keep the page static and add JavaScript only where it is needed.
Adding a UI Framework
Installing an Integration
Add a framework with one command:
npx astro add preact
- The command installs
@astrojs/preactandpreact. - It adds
preact()tointegrationsinastro.config.mjs. - For React use
npx astro add react. For Vue,vue. For Svelte,svelte. - You may use more than one framework on the same site.
This course uses Preact. It looks like React (same JSX and hooks) but is about 4 KB. Everything you learn here works the same way with React.
A Framework Component Without a Directive
- You can use a
.tsxor.jsxcomponent in an.astropage, like an Astro component. - With no
client:*directive, Astro renders it to HTML on the server and sends no JavaScript. - The button below shows, but clicking does nothing.
Press ▶ Run and click the first button. Nothing happens. The second button works.
- Open the HTML tab. Only the island is wrapped in
<astro-island>. <astro-island>is the element Astro uses to find and wake up an island.
When the Integration Is Missing
- Without the integration, Astro does not know how to render
.tsxfiles. - The build stops with "No valid renderer was found".
Press ▶ Run to see the error. Then add integrations: [preact()] to fix it.
Simple lesson: add the framework with npx astro add. A component without client:* is only HTML.
Client Directives
The Five Directives
| Directive | When the island wakes up | Good for |
|---|---|---|
client:load | As soon as the page loads | Things needed at once: a menu button |
client:idle | When the browser is free | Less urgent things: a chat widget |
client:visible | When it scrolls into view | Things far down: comments, a footer form |
client:media="(query)" | When a CSS media query matches | Mobile-only parts: a hamburger menu |
client:only="preact" | Only in the browser, never on the server | Code that needs window |
client:loadis the most eager. Use it only for things needed at once.client:idleandclient:visiblekeep the first load light.client:mediasaves JavaScript on screens that do not need the island.client:onlyskips server HTML. The island is empty until JavaScript runs.
Animation: a timeline from page load. client:load wakes first, client:idle a little later, client:visible only when the reader scrolls to it.
page loads time →
client:load
client:idle
client:visible
Each island wakes at its own time. Until then it is plain HTML.
Press ▶ Run. Read the Terminal to see the order the islands wake up in.
- The clocks start at slightly different times.
WindowWidthuseswindow, which does not exist on the server. So it needsclient:only.
Do not add client:load to everything "just in case". Each island adds JavaScript. Start with no directive, and add the laziest one that works.
Simple lesson: pick the laziest directive that still works: often client:visible or client:idle.
Props and Children
Passing Props
- Pass props to an island like any component:
<Counter start={5} client:load />. - Astro sends the props to the browser as data, so the island can hydrate with the same values.
- So props must be data: text, numbers, booleans, arrays, plain objects,
Date,Map,Set. - Functions cannot be passed to a hydrated island.
Passing Children
- Content between the tags arrives in the component as
children. - That content is rendered by Astro as static HTML. It can even contain Astro components.
Press ▶ Run. The island gets a number prop and some HTML children.
- [JS] In
Spoiler.jsx, leave outinterface Propsand the: Propstype.
A Function Prop Breaks the Island
Press ▶ Run. The build stops, because a function cannot travel to the browser.
- Fix: keep the click code inside the island.
Simple lesson: island props must be plain data. Children arrive as static HTML.
Sharing State Between Islands
Islands Are Separate
- Each island is its own small app.
- Two islands cannot read each other's
useState. - To share data, both islands import the same store module.
A Tiny Shared Store
- A store holds a value and tells listeners when it changes.
- Real projects often use the small nanostores library (
npm install nanostores @nanostores/preact). - Below, a 10-line store does the same job, so you can see how it works.
Press ▶ Run. Click Add to cart in one island. The badge in the other island updates.
- Both islands imported
../stores/cart, so they share onecount. useEffect(() => subscribe(setN), [])re-renders the badge on every change.
With nanostores the same idea is const cart = atom(0) in the store, and useStore(cart) in each island.
Simple lesson: islands share data through a store module that both import.
Island or Script?
Choosing the Right Tool
- A
<script>in an.astrofile is enough for small behaviour: toggle a class, copy text, open a menu. - An island is better when the UI has state that changes what is drawn: a cart, a filter list, a form with live checks.
- An island is also right when you already have a framework component you want to reuse.
| Need | Use |
|---|---|
| Toggle a menu open and closed | <script> |
| Copy a code block to the clipboard | <script> |
| A search box that filters a list as you type | Island |
| A cart shared by the header and the page | Islands + a store |
| A chart library made for React | Island (client:visible) |
Islands render on the server first, so readers see the HTML even before JavaScript loads. Only client:only islands start empty.
Simple lesson: use a <script> for small actions and an island for UI with changing state.
TL;DR
- Island = one interactive component on a static page. The rest sends no JavaScript.
- Framework = add one with
npx astro add preact(or react, vue, svelte). - No directive = server HTML only, no JavaScript.
- client:load = at once. client:idle = when the browser is free. client:visible = when seen.
- client:media = when a media query matches. client:only="preact" = browser only.
- Props = plain data only, no functions. Children = static HTML.
- Shared state = a store module both islands import (nanostores in real projects).
- Small action =
<script>. Changing UI state = island.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
No client:* directive | A framework component is always interactive | Add a directive | Clicks do nothing |
client:load on every island | Eager is always better | Use idle or visible when you can | Slower first load |
client: on an .astro component | Astro components can hydrate | Use a framework component | The directive does nothing |
| Passing a function prop | Props can be anything | Only data can be sent | Build error |
Using window without client:only | The component runs only in the browser | Islands render on the server first | window is not defined |
| No framework integration | .tsx just works | Run npx astro add preact | "No valid renderer was found" |
Expecting two islands to share useState | Islands are one app | Use a shared store | Each island has its own value |
Check Yourself
Exercises
What to Remember
- An island is one interactive framework component. The rest of the page stays HTML.
- Add a framework with
npx astro add. No directive means no JavaScript. - Choose the laziest directive:
visibleoridlebeforeload. Useonlyfor browser-only code. - Props must be data. Children arrive as HTML.
- Share state between islands with a store module.
Next: Page Navigation, where pages swap smoothly without a full reload.