Skip to content

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:

html
<!-- 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:

html
<!-- 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:

html
<!-- 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:

html
<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:

javascript
// 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:

FrameworkBundle SizeWann verwenden
Vanilla JS0kbDu brauchst kein Framework, pures JavaScript reicht
Svelte~2-5kbDu magst kompilierte, reaktive UI
React~45kbDu kennst React bereits, großes Ökosystem
Preact~4kbDu willst React, aber kleiner
Vue~35kbDu bevorzugst Vue's Syntax und Features
Lit~8kbDu möchtest Web Components Standard
Alpine.js~15kbDu 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

bash
wm generate
? Component name: Newsletter
? Island framework: Vanilla JS  # Choose your framework

2. Island-Datei erstellt

javascript
// 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

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

bash
wm dev
# Visit http://localhost:3032
# See your island in action!

Island-Lifecycle

1. Konstruktion

javascript
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

javascript
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

javascript
async handleClick() {
  // Update UI
  this.updateDisplay();

  // Call API
  await fetch('/api/endpoint');

  // Update state
  this.state.count++;
}

4. Aufräumen

javascript
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:

html
<!-- component.html -->
<div
  data-island="product-card"
  data-island-props='{
    "productId": "{{id}}",
    "title": "{{title}}",
    "price": {{price}},
    "inStock": {{inStock}}
  }'
>
</div>

Island empfängt:

javascript
// 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:

html
<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:

html
<!-- 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.5s

Islands 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:

javascript
// 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

javascript
// 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:

  1. Bündelt die Island neu
  2. Zerstört die alte Instanz
  3. Erstellt neue Instanz
  4. Bewahrt Seitenstatus

3. Update sofort sehen

Browser aktualisiert ohne vollständigen Seiten-Reload!

Fehlerbehandlung

Island-Ladefehler

javascript
// 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

javascript
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

javascript
// ❌ 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

javascript
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

javascript
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:

html
<!-- 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:

Webmate Studio Component Development Dokumentation