Islands: Basics

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

  1. Page loads with server-rendered HTML (instant display)

  2. Luna loader detects luna:id elements

  3. Based on trigger, loads /static/counter.js

  4. JavaScript takes over, element becomes interactive

Island Attributes

AttributePurpose
luna:idComponent identifier for hydration
luna:urlURL to load component JavaScript
luna:stateSerialized props (JSON)
luna:client-triggerWhen 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

MetricTraditional SPAIslands
Initial JS100KB+~3KB loader
TTISlowFast
LCPBlocked by JSImmediate
InteractivityAll or nothingProgressive

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 โ†’