Islands Basics
Islands Architecture enables partial hydration - only interactive parts of your page load JavaScript.
The Problem
Traditional SPAs send JavaScript for the entire page:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Header (static) โ JS loaded โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Article (static) โ JS loaded โ
โ โ
โ Comments (interactive)โ JS needed โ
โ โ
โ Footer (static) โ JS loaded โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Result: Large bundle, slow load, wasted resources.
The Solution
Islands hydrate only interactive components:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Header (static) โ No JS โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Article (static) โ No JS โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Comments Island โ JS โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Footer (static) โ No JS โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Result: Minimal JavaScript, fast load, great Core Web Vitals.
Creating an Island
1. Server-Rendered HTML
The server renders an island with hydration attributes:
<div
luna:id="counter"
luna:url="/static/counter.js"
luna:state="0"
luna:client-trigger="load"
>
<button>Count: 0</button>
</div>
For server-side rendering with MoonBit, see the MoonBit Tutorial.
2. Client Side (TypeScript)
Create the interactive component:
// counter.ts
import { createSignal, hydrate } from '@luna_ui/luna';
interface CounterProps {
initial: number;
}
function Counter(props: CounterProps) {
const [count, setCount] = createSignal(props.initial);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count()}
</button>
);
}
// Register for hydration
hydrate("counter", Counter);
3. HTML Output
The server renders:
<div
luna:id="counter"
luna:url="/static/counter.js"
luna:state="0"
luna:client-trigger="load"
>
<button>Count: 0</button>
</div>
4. Hydration
Page loads with server-rendered HTML (instant display)
Luna loader detects
luna:idelementsBased on trigger, loads
/static/counter.jsJavaScript takes over, element becomes interactive
Island Attributes
| Attribute | Purpose |
|---|---|
luna:id | Component identifier for hydration |
luna:url | URL to load component JavaScript |
luna:state | Serialized props (JSON) |
luna:client-trigger | When to hydrate |
How Hydration Works
Server HTML Luna Loader Island Component
โ โ โ
โ luna:id found โ โ
โ โโโโโโโโโโโโโโโโโโ>โ โ
โ โ โ
โ โ Check trigger โ
โ โ (load/idle/visible) โ
โ โ โ
โ โ Load JS module โ
โ โโโโโโโโโโโโโโโโโโโโโโ> โ
โ โ โ
โ โ Call hydrate() โ
โ โ<โโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ<โโโโโโโโโโโโโโโโโโโโ Take over DOM โ
โ Interactive! โ โ
Multiple Islands
Each island is independent. A typical page structure:
<div>
<h1>My Page</h1>
<!-- Search island - hydrates immediately -->
<div luna:id="search" luna:client-trigger="load">...</div>
<!-- Article - pure HTML, no JS -->
<article>
<p>Static content...</p>
</article>
<!-- Comments island - hydrates when visible -->
<div luna:id="comments" luna:client-trigger="visible">...</div>
<!-- Footer - pure HTML -->
<footer>...</footer>
</div>
Benefits
| Metric | Traditional SPA | Islands |
|---|---|---|
| Initial JS | 100KB+ | ~3KB loader |
| TTI | Slow | Fast |
| LCP | Blocked by JS | Immediate |
| Interactivity | All or nothing | Progressive |
When to Use Islands
Use Islands for:
Interactive widgets (forms, search, comments)
Components needing client state
Dynamic content after load
Don't use Islands for:
Static content (articles, headers)
Content that doesn't need interactivity
Server-only rendered pages
Try It
Think about a typical blog page. Which parts would you make into islands?
Answer
Blog Page Structure:
โโโ Header โ Static (no island)
โโโ Navigation โ Static (no island)
โโโ Article โ Static (no island)
โโโ Share Buttons โ Island (click tracking)
โโโ Comments Form โ Island (form submission)
โโโ Comments List โ Island (live updates)
โโโ Related Posts โ Static (no island)
โโโ Footer โ Static (no island)
Only 3 islands needed for full interactivity!
Next
Learn about Hydration Triggers โ