@ilha/astro is an Astro integration for Ilha islands. You register it once, then import any island into an .astro page and hydrate it with a client directive.
It is a separate package from ilha. Add it when you build with Astro and want Ilha islands as first-class Astro components.
Install
npm i @ilha/astroyarn add @ilha/astropnpm add @ilha/astrobun add @ilha/astroastro and ilha are peer dependencies. Install both alongside @ilha/astro.
Quick start
Add the integration to your Astro config:
// astro.config.mjs
import { defineConfig } from "astro/config";
import ilha from "@ilha/astro";
export default defineConfig({
integrations: [ilha()],
});Islands in these examples use JSX. Set jsx and jsxImportSource in tsconfig.json (or use /** @jsxImportSource ilha */ per file) — see .render() — JSX setup:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "ilha"
}
}Define an island as usual, then use it in a page with a client directive:
// src/islands/counter.tsx
import ilha from "ilha";
export const Counter = ilha
.input<{ start?: number }>()
.state("count", (input) => input.start ?? 0)
.action("increment", (_, { state }) => {
state.count((count) => count + 1);
})
.render(({ state, action }) => (
<button type="button" onclick={action.increment}>
{state.count()}
</button>
));---
import { Counter } from "../islands/counter";
---
<Counter client:load start={10} />Astro SSR-renders the island on the server. On the client, @ilha/astro calls the island’s own .mount() against the existing markup — no flicker, and .onMount() is skipped when the state snapshot already matches the DOM.
How it works
| Phase | What happens |
|---|---|
| Server | The renderer calls .hydratable(), which wraps SSR output in a [data-ilha] host with serialized props and state. |
| Client | The client entry finds that [data-ilha] host inside Astro’s <astro-island> and calls .mount() for hydration. |
Hydration is driven by Ilha’s data-ilha-* attributes, so every Astro client directive works as expected.
server client
────────────────────────────────── ────────────────────────────────────
.renderToStaticMarkup(island) @ilha/astro/client.js
→ island.hydratable(props, …) → find [data-ilha] inside <astro-island>
→ data-ilha-state snapshot → island.mount(host)
→ data-ilha-props on host → astro:unmount → unmount()Client directives
| Directive | Behavior |
|---|---|
client:load |
Hydrate as soon as the page loads. |
client:idle |
Hydrate when the browser is idle. |
client:visible |
Hydrate when the island enters the viewport. |
client:media |
Hydrate when a media query matches. |
client:only |
Skip SSR. Mount fresh in the browser with the props Astro passes in. |
Use client:only when the island needs browser-only APIs during first paint. For everything else, prefer a directive that SSR-renders first so users see content before JavaScript runs.
---
import { Counter } from "../islands/counter";
---
<Counter client:only="ilha" start={0} />Plain function components
The renderer also accepts plain functions that return Ilha RawHtml or a string — the same shape Areia’s static exports use (for example Button without callbacks).
On the server, @ilha/astro calls the function and emits the HTML with no data-ilha wrapper. With SSR-backed directives such as client:load, the client re-invokes the function so libraries that schedule auto-bind against SSR markup (for example data-areia-* hosts) still wire up.
Plain Astro function components do not create an Ilha island lifecycle. Lowercase event props such as onclick therefore stay inert when you render the function directly through Astro. Plain functions also cannot use client:only, because there is no island to mount without SSR markup.
Promote independently interactive UI with ilha(() => JSX); use the builder form when it needs state, actions, or other local capabilities. Plain function children rendered inside an island inherit its event lifecycle.
export const Button = ({ label }: { label: string }) => (
<button class="btn" type="button">
{label}
</button>
);---
import { Button } from "../components/button";
---
<Button client:load label="Save" />If client re-invocation fails, the integration throws a sanitized [@ilha/astro] error and does not expose the original exception.
Import paths
| Import path | Use it for |
|---|---|
@ilha/astro |
Astro integration (ilha() / getRenderer()). |
@ilha/astro/client.js |
Client hydration entry (registered by Astro). |
@ilha/astro/server.js |
Server renderer entry (registered by Astro). |
You almost never import the client or server entrypoints yourself. ilha() registers them through Astro’s renderer API.
Notes
- Ilha islands do not support Astro’s
<slot />or children forwarding. Pass data through props and render inside the island’s.render()function. client:onlymounts withComponent.mount(element, props)against an empty host. Prefer SSR + hydrate when you can.- Peer ranges:
astro^7.0.0,ilha>=0.9.5.
Related
| Topic | Guide |
|---|---|
Core ilha API |
ilha |
| Hydratable SSR | Hydratable |
| Mount and discovery | Mount |
| Shared cross-island state | Store |
| Multi-page SPA routing | Router |