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