---
title: Islands
description: >-
  Lege eine interaktive Komponente in islands/ ab und nutze sie in jeder MDX-Seite — automatisch hydriert, ohne Import pro Seite.
---

Blume rendert deine Dokumentation standardmäßig als statisches HTML mit **null JavaScript**. Wenn du etwas Interaktives brauchst — eine Live-Demo, ein Diagramm, einen Playground — fügst du eine **Island** hinzu: eine Framework-Komponente, die JS nur für sich selbst ausliefert, und nur auf den Seiten, die sie verwenden.

## Die `islands/`-Konvention [#the-islands-convention]

Lege eine Komponente in einen `islands/`-Ordner im Stammverzeichnis deines Projekts. Ihr Dateiname wird zu einer Komponente, die du in **jeder** `.mdx`-Seite verwenden kannst, ganz ohne Import:

```tsx islands/Counter.tsx lineNumbers
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Clicked {count}</button>;
}
```

```mdx page.mdx
Here's a live counter: <Counter />
```

Der Dateiname ist der Komponentenname, er **muss also ein PascalCase-Bezeichner sein** — nur Buchstaben, Ziffern und Unterstriche (`Counter.tsx` → `<Counter />`). Kleingeschriebene Dateinamen, Namen mit Bindestrichen/Punkten/Leerzeichen (wie `Time-Picker.tsx`) und zwei Islands, die zum selben Namen aufgelöst werden, werden mit einer Build-Warnung übersprungen.

:::note
Islands sind für **interaktive** UI gedacht. Für eine statische Komponente, die du seitenübergreifend wiederverwendest (ein gestalteter Hinweis, eine Preistabelle), nutze stattdessen ein [MDX-Override](/docs/configuration/customization) — es liefert kein JavaScript aus.
:::

## Islands in `components.ts` registrieren [#registering-islands-in-componentsts]

Wenn du Islands lieber bei deinen übrigen Komponenten aufbewahren möchtest — oder ihnen einen anderen Namen als der Datei geben willst — registriere sie mit `defineComponents`. Die Gruppe `islands` verhält sich genau wie der `islands/`-Ordner: Jeder Eintrag steht in jeder MDX-Seite zur Verfügung und wird hydriert (standardmäßig mit `client: "visible"`).

```ts components.ts
import { defineComponents } from "blume";
import Counter from "./widgets/Counter.tsx";

export default defineComponents({
  islands: {
    Counter, // <Counter /> in any MDX page, hydrated
  },
});
```

Referenziere die Komponente per Import oder über einen Pfad-String und lege mit der Deskriptor-Form einen Hydrierungsmodus pro Island fest:

```ts components.ts
export default defineComponents({
  islands: {
    Chart: { component: "./widgets/Chart.tsx", client: "only" },
  },
});
```

## Hydrierung [#hydration]

Standardmäßig nutzt eine Island `client:visible`: Sie hydriert, sobald die lesende Person sie in den sichtbaren Bereich scrollt, sodass auch eine Seite voller Islands sofort lädt. Entscheide dich mit einem `export const client` in der Island-Datei für eine andere Strategie:

```tsx islands/Chart.tsx lineNumbers
// Skip server rendering entirely — for components that touch the DOM/window.
export const client = "only";

export default function Chart() {
  /* ... */
}
```

| `client`-Wert | Hydriert | Verwende es für |
| --- | --- | --- |
| `"visible"` _(Standard)_ | Beim Scrollen in den sichtbaren Bereich | Die meisten Islands |
| `"load"` | Sofort beim Laden der Seite | UI oberhalb der Falz, die sofort da sein muss |
| `"idle"` | Wenn der Haupt-Thread untätig ist | Nicht dringende Interaktivität |
| `"only"` | Nur clientseitig, nie serverseitig gerendert | Bibliotheken, die `window`/`document` brauchen (Diagramme, Editoren) |

## Frameworks

**React funktioniert sofort** — Blume aktiviert es automatisch, sobald dein Projekt eine `.tsx`/`.jsx`-Island enthält.

Der [React Compiler](https://react.dev/learn/react-compiler) ist standardmäßig aktiv, sobald React aktiviert ist, sodass deine Islands automatisch memoisiert werden — handgeschriebene `useMemo`/`useCallback` sind nicht nötig. Er wird mit Blume ausgeliefert; es gibt nichts zu installieren. Deaktivieren kannst du ihn in `blume.config.ts`:

```ts blume.config.ts
export default defineConfig({
  react: { compiler: false },
});
```

**Vue und Svelte** werden ebenfalls unterstützt; installiere die passende Astro-Integration, und Blume verdrahtet den Renderer, sobald es eine `.vue`- oder `.svelte`-Island sieht:

```bash
# Vue
npm install @astrojs/vue vue

# Svelte
npm install @astrojs/svelte svelte
```

```vue islands/Toggle.vue lineNumbers
<script setup>
import { ref } from "vue";
const on = ref(false);
</script>

<template>
  <button @click="on = !on">{{ on ? "On" : "Off" }}</button>
</template>
```

Props, die du in MDX übergibst (`<Counter start={5} />`), werden an die Komponente weitergereicht, und Kindelemente (`<Counter>label</Counter>`) kommen als Standard-Slot an.

:::tip
Islands hydrieren auf dem Client, daher muss alles, was du als Prop übergibst, serialisierbar sein — Strings, Zahlen, einfache Objekte, keine Funktionen.
:::

## Hooks

Islands hydrieren eigenständig, es gibt also keinen React-Context, durch den sich Projektdaten durchreichen ließen. Stattdessen liest `blume/hooks` einen kleinen Snapshot, den das Layout in die Seite serialisiert — kein Prop-Drilling nötig:

```tsx islands/PageInfo.tsx lineNumbers
import { useBlume, usePage } from "blume/hooks";

export default function PageInfo() {
  const blume = useBlume();
  const page = usePage();
  if (!(blume && page)) {
    return null;
  }
  return (
    <p>
      You're reading <strong>{page.title}</strong> on {blume.config.title}.
    </p>
  );
}
```

| Hook | Gibt zurück |
| --- | --- |
| `useBlume()` | `{ config, navigation }` für die Website, oder `null` vor dem Mount |
| `usePage()` | `{ route, title }` für die aktuelle Seite, oder `null` vor dem Mount |
| `useSearch()` | `{ search, results, loading }` — den konfigurierten Suchanbieter abfragen |
| `useAskAI()` | `{ ask, messages, loading, reset }` — vom Ask-AI-Endpunkt streamen |

`useBlume()` und `usePage()` geben `null` zurück, bis die Island mountet (damit Server und Client denselben ersten Frame rendern) — sichere das ab. Der Snapshot wird nur auf Seiten ausgegeben, die React ausliefern, eine vollständig statische Website zahlt also nichts dafür.

Auf [benutzerdefinierten Seiten](/docs/advanced/custom-pages), die mit `PageLayout` gebaut sind, übergib `clientData`, damit Islands dort darauf zugreifen können:

```astro
<PageLayout
  clientData={{ config: data.config, navigation: data.navigation, page: { route: "/", title: "Home" } }}
  {/* …other props… */}
/>
```
