Islands Architecture
Lerne wie Islands Interaktivität zu deinen Components mit minimalem JavaScript hinzufügen.
Was sind Islands?
Islands sind isolierte, interaktive Components, die unabhängig auf dem Client hydrieren.
Das traditionelle Problem
Traditionelle SPAs liefern das gesamte JavaScript an jeden Benutzer:
<!-- Traditional SPA: Everything is JavaScript -->
<div id="app"></div>
<script src="bundle.js"></script>
<!-- 500kb+ JavaScript for a simple blog post! -->Probleme:
- ❌ Langsamer initialer Ladevorgang (Download großer Bundles)
- ❌ Langsame Time to Interactive (Parsen/Ausführen von JS)
- ❌ Schlechtes SEO (Content nicht im HTML)
- ❌ Verschwendete Bandbreite (JS für statischen Content)
Die Islands-Lösung
Liefere standardmäßig kein JavaScript. Füge Interaktivität nur dort hinzu, wo sie benötigt wird:
<!-- Static HTML (loads instantly) -->
<header class="bg-white border-b">
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<article>
<h1>Blog Post Title</h1>
<p>Static content renders immediately...</p>
</article>
<!-- Island: Only this part is interactive -->
<div data-island="comments" data-island-props='{"postId": "123"}'>
<!-- 12kb JavaScript, lazy-loaded when needed -->
</div>
</main>
<footer class="bg-gray-900 text-white">
<p>© 2024 Company</p>
</footer>Vorteile:
- ✅ Schneller initialer Ladevorgang (statisches HTML)
- ✅ Schnelle Time to Interactive (minimales JS)
- ✅ Gutes SEO (Content im HTML)
- ✅ Optimale Bandbreitennutzung
Wie Islands funktionieren
1. Server rendert statisches HTML
Dein Component-HTML wird auf dem Server gerendert:
<!-- component.html -->
<div class="hero">
<h1 class="text-4xl font-bold">{{title}}</h1>
<p class="text-gray-600">{{description}}</p>
<!-- Island marker -->
<div data-island="newsletter" data-island-props='{"title": "{{title}}"}'>
<!-- Placeholder -->
</div>
</div>Output:
<div class="hero">
<h1 class="text-4xl font-bold">Welcome</h1>
<p class="text-gray-600">Sign up for our newsletter</p>
<div data-island="newsletter" data-island-props='{"title": "Welcome"}'>
<!-- Will be hydrated by JavaScript -->
</div>
</div>2. Browser lädt die Seite
Benutzer sehen statisches HTML sofort:
Time: 0ms
┌─────────────────────────────┐
│ Welcome │
│ Sign up for our newsletter │
│ │
│ [Newsletter form loads...] │
└─────────────────────────────┘
Seite ist sichtbar und lesbar!3. Island JavaScript lädt
Island-Script lädt asynchron:
// islands/newsletter.js (12kb)
export default class NewsletterIsland {
constructor(element, props) {
// Initialize interactive form
this.renderForm(element, props);
}
}4. Island hydriert
JavaScript übernimmt das Island-Element:
Time: 500ms
┌─────────────────────────────┐
│ Welcome │
│ Sign up for our newsletter │
│ │
│ [Email Input] [Subscribe] │ ← Jetzt interaktiv!
└─────────────────────────────┘Wann Islands verwenden
✅ Verwende Islands für
Formulare mit Validierung
html<div data-island="contact-form"> <!-- Email validation, submission, error handling --> </div>Bild-Karussells/Slider
html<div data-island="image-slider" data-island-props='{"images": [...]}'> <!-- Swipe gestures, autoplay, navigation --> </div>Interaktive Charts
html<div data-island="analytics-chart" data-island-props='{"data": [...]}'> <!-- Real-time data visualization --> </div>Such- und Filter-UI
html<div data-island="product-filter"> <!-- Dynamic filtering, sorting, pagination --> </div>Modals, Dropdowns, Tooltips
html<div data-island="navigation-menu"> <!-- Dropdown menus, mobile hamburger --> </div>Counter, Timer, Live-Updates
html<div data-island="countdown-timer" data-island-props='{"endDate": "2024-12-31"}'> <!-- Live countdown --> </div>
❌ Verwende Islands nicht für
Statischen Text-Content
html<!-- ❌ Schlecht: Benötigt kein JavaScript --> <div data-island="static-text"> <p>Just some text</p> </div> <!-- ✅ Gut: Einfaches HTML --> <p class="text-gray-600">Just some text</p>Einfache Links/Buttons
html<!-- ❌ Schlecht: Natives HTML funktioniert einwandfrei --> <div data-island="link"> <a href="/about">About</a> </div> <!-- ✅ Gut: Standard HTML --> <a href="/about" class="text-blue-600 hover:underline">About</a>Bilder
html<!-- ❌ Schlecht: Keine Interaktion benötigt --> <div data-island="image"> <img src="photo.jpg" alt="Photo"> </div> <!-- ✅ Gut: Natives img-Tag --> <img src="photo.jpg" alt="Photo" loading="lazy">Reine CSS-Animationen
html<!-- ❌ Schlecht: CSS bewältigt dies --> <div data-island="fade-in"> <div class="content">Fades in</div> </div> <!-- ✅ Gut: CSS Animation --> <div class="opacity-0 animate-fade-in"> <div class="content">Fades in</div> </div>
Framework-Wahl
Wähle ein Framework für dein Projekt - dasselbe für alle Islands:
| Framework | Bundle Size | Wann verwenden |
|---|---|---|
| Vanilla JS | 0kb | Du brauchst kein Framework, pures JavaScript reicht |
| Svelte | ~2-5kb | Du magst kompilierte, reaktive UI |
| React | ~45kb | Du kennst React bereits, großes Ökosystem |
| Preact | ~4kb | Du willst React, aber kleiner |
| Vue | ~35kb | Du bevorzugst Vue's Syntax und Features |
| Lit | ~8kb | Du möchtest Web Components Standard |
| Alpine.js | ~15kb | Du magst deklarative, HTML-first Syntax |
Ein Framework pro Projekt
In der Praxis nutzt du ein Framework für alle Islands in deinem Projekt. Ein React-Entwickler bleibt bei React, ein Vue-Entwickler bei Vue. Das vereinfacht Entwicklung, Build und Wartung.
Eine Island erstellen
1. Component mit Island generieren
wm generate
? Component name: Newsletter
? Island framework: Vanilla JS # Choose your framework2. Island-Datei erstellt
// components/Newsletter/islands/newsletter.js
export default class NewsletterIsland {
constructor(element, props = {}) {
this.element = element;
this.props = props;
this.init();
}
init() {
// Your island code
}
destroy() {
// Cleanup when island is removed
}
}3. Verwendung im HTML
<!-- components/Newsletter/component.html -->
<div class="newsletter">
<h2 class="text-2xl font-bold mb-6">{{title}}</h2>
<!-- Island marker -->
<div
data-island="newsletter"
data-island-props='{"title": "{{title}}"}'
>
<!-- Island initializes here -->
</div>
</div>4. Teste mit dem Dev-Server
wm dev
# Visit http://localhost:3032
# See your island in action!Island-Lifecycle
1. Konstruktion
constructor(element, props = {}) {
this.element = element; // DOM element
this.props = props; // Data from component
// Access props
const title = this.props.title;
const config = this.props.config;
}2. Initialisierung
init() {
// Render HTML
this.element.innerHTML = `<div>...</div>`;
// Add event listeners
this.element.querySelector('button').addEventListener('click', () => {
this.handleClick();
});
// Fetch data
this.loadData();
}3. Benutzer-Interaktion
async handleClick() {
// Update UI
this.updateDisplay();
// Call API
await fetch('/api/endpoint');
// Update state
this.state.count++;
}4. Aufräumen
destroy() {
// Remove event listeners
this.element.querySelector('button')?.removeEventListener('click', this.handleClick);
// Cancel pending requests
this.abortController?.abort();
// Clear timers
clearInterval(this.interval);
}Props-Übergabe
Component zu Island
Übergebe Daten vom Component zur Island:
<!-- component.html -->
<div
data-island="product-card"
data-island-props='{
"productId": "{{id}}",
"title": "{{title}}",
"price": {{price}},
"inStock": {{inStock}}
}'
>
</div>Island empfängt:
// islands/product-card.js
constructor(element, props) {
console.log(props.productId); // "abc123"
console.log(props.title); // "Product Name"
console.log(props.price); // 29.99
console.log(props.inStock); // true
}Dynamische Props
Props verwenden Mustache-Syntax:
<div
data-island="user-profile"
data-island-props='{
"userId": "{{userId}}",
"name": "{{userName}}",
"avatar": "{{userAvatar}}",
"verified": {{userVerified}}
}'
>
</div>Mehrere Islands
Components können mehrere Islands haben:
<!-- component.html -->
<div class="page">
<!-- Navigation island -->
<div data-island="mobile-menu" class="md:hidden">
<!-- Mobile navigation with hamburger -->
</div>
<!-- Hero content (static) -->
<section class="hero">
<h1>{{title}}</h1>
<p>{{description}}</p>
</section>
<!-- Newsletter island -->
<div data-island="newsletter-form">
<!-- Email subscription form -->
</div>
<!-- Image gallery island -->
<div data-island="image-gallery" data-island-props='{"images": {{images}}}'>
<!-- Interactive image gallery -->
</div>
<!-- Comments island -->
<div data-island="comments" data-island-props='{"postId": "{{postId}}"}'>
<!-- Comment system with replies -->
</div>
</div>Jede Island:
- Lädt unabhängig
- Verwendet ihr eigenes Framework
- Hat ihren eigenen Lifecycle
- Beeinflusst andere Islands nicht
Performance
Bundle-Größen-Vergleich
Traditionelle SPA:
Initial Load:
HTML: 2kb
CSS: 45kb
JS: 350kb (entire app)
Total: 397kb
Time to Interactive: 3.5sIslands Architecture:
Initial Load:
HTML: 15kb (pre-rendered content)
CSS: 8kb (purged Tailwind)
JS: 0kb (no islands yet)
Total: 23kb
Time to Interactive: 0.2s
After User Interaction:
+ Newsletter Island: 4kb
+ Comments Island: 12kb
Total: 39kb (10x smaller!)Ladestrategie
Islands laden on-demand:
// Lazy load when island enters viewport
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadIsland(entry.target);
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('[data-island]').forEach(el => {
observer.observe(el);
});HMR (Hot Module Reload)
Islands unterstützen sofortige Updates während der Entwicklung:
1. Island-Code bearbeiten
// islands/counter.js
init() {
this.element.innerHTML = `
<button class="px-4 py-2 bg-blue-600 text-white rounded">
Count: ${this.count} <!-- Change something -->
</button>
`;
}2. Datei speichern
HMR erkennt die Änderung und:
- Bündelt die Island neu
- Zerstört die alte Instanz
- Erstellt neue Instanz
- Bewahrt Seitenstatus
3. Update sofort sehen
Browser aktualisiert ohne vollständigen Seiten-Reload!
Fehlerbehandlung
Island-Ladefehler
// Island fails to load
export default class MyIsland {
constructor(element, props) {
try {
this.init();
} catch (error) {
this.showError(error);
}
}
showError(error) {
this.element.innerHTML = `
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
<p>Failed to load component</p>
<button onclick="location.reload()">Reload</button>
</div>
`;
console.error('Island error:', error);
}
}API-Fehler
async loadData() {
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('API error');
const data = await response.json();
this.render(data);
} catch (error) {
this.element.innerHTML = `
<div class="text-red-600">
Failed to load data. Please try again.
</div>
`;
}
}Best Practices
1. Halte Islands klein
// ❌ Schlecht: Eine massive Island
export default class MegaIsland {
// 1000+ lines of code
// Does everything
}
// ✅ Gut: Kleine, fokussierte Islands
export default class NewsletterIsland {
// 50 lines
// Does one thing well
}2. Räume Ressourcen auf
destroy() {
// ✓ Remove event listeners
this.removeEventListeners();
// ✓ Cancel API requests
this.abortController?.abort();
// ✓ Clear timers
clearInterval(this.pollInterval);
// ✓ Unsubscribe from stores
this.unsubscribe?.();
}3. Behandle Ladezustände
async init() {
// Show loading state
this.element.innerHTML = `
<div class="flex items-center justify-center p-8">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
`;
// Load data
const data = await this.fetchData();
// Show content
this.render(data);
}4. Progressive Enhancement
Islands sollten erweitern, nicht ersetzen:
<!-- Works without JavaScript -->
<form action="/api/newsletter" method="POST">
<input type="email" name="email" required>
<button type="submit">Subscribe</button>
</form>
<!-- Island enhances with client-side validation -->
<div data-island="newsletter-form">
<!-- JavaScript adds inline validation, AJAX submission -->
</div>Nächste Schritte
Wähle dein Framework und tauche tief ein:
- Vanilla JS - Kein Framework, pures JavaScript
- Svelte - Reaktive UI mit Compiler
- React - Beliebte Library mit großem Ökosystem
- Preact - React-kompatibel, 10x kleiner
- Vue - Progressives Framework
- Lit - Web Components Standard
- Alpine.js - Minimal, deklarativ
Oder erkunde:
- Component Basics - Component-Struktur
- Examples - Praxisbeispiele für Islands
- Best Practices - Schreibe besseren Code