---
title: アイランド
description: >-
  interactive なコンポーネントを islands/ に置くだけで、任意の MDX ページで使えます — 自動的にハイドレートされ、ページごとの import は不要です。
---

Blume はデフォルトでドキュメントを **JavaScript ゼロ** の静的 HTML としてレンダリングします。インタラクティブな要素が必要になったとき — ライブデモ、チャート、プレイグラウンドなど — に追加するのが **アイランド** です。アイランドとは、自分自身のための JS のみを、それを使っているページにのみ配信するフレームワークコンポーネントです。

## `islands/` の規約 [#the-islands-convention]

プロジェクトルートの `islands/` フォルダにコンポーネントを置きます。そのファイル名がコンポーネント名となり、import なしで **任意の** `.mdx` ページで使えるようになります:

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

ファイル名がコンポーネント名になるため、**PascalCase の識別子でなければなりません** — 使えるのは英字、数字、アンダースコアのみです(`Counter.tsx` → `<Counter />`)。小文字始まりのファイル名、ダッシュ・ドット・スペースを含む名前(`Time-Picker.tsx` など)、および同じ名前に解決される 2 つのアイランドは、ビルド警告とともにスキップされます。

:::note
アイランドは **インタラクティブな** UI のためのものです。複数ページで再利用する静的なコンポーネント(スタイル付きのコールアウト、料金表など)には、代わりに [MDX オーバーライド](/docs/configuration/customization) を使ってください — こちらは JavaScript を一切配信しません。
:::

## `components.ts` でのアイランド登録 [#registering-islands-in-componentsts]

アイランドを他のコンポーネントと同じ場所に置きたい場合や、ファイル名とは異なる名前を付けたい場合は、`defineComponents` で登録します。`islands` グループは `islands/` フォルダとまったく同じです: 各エントリはすべての MDX ページで利用でき、ハイドレートされます(デフォルトは `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
  },
});
```

コンポーネントは import 経由でもパス文字列でも参照でき、ディスクリプタ形式を使えばアイランドごとにハイドレーションモードを設定できます:

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

## ハイドレーション [#hydration]

デフォルトでは、アイランドは `client:visible` を使います: 読者がスクロールしてビューに入った時点でハイドレートされるため、アイランドだらけのページでも即座に読み込まれます。別の戦略を選ぶには、アイランドのファイル内で `export const client` を指定します:

```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` の値 | ハイドレートのタイミング | 用途 |
| --- | --- | --- |
| `"visible"` _(デフォルト)_ | スクロールでビューに入ったとき | ほとんどのアイランド |
| `"load"` | ページ読み込み時に即座に | ファーストビュー、即応が必須の UI |
| `"idle"` | メインスレッドがアイドルになったとき | 緊急性の低いインタラクティブ要素 |
| `"only"` | クライアントのみ、サーバーレンダリングなし | `window`/`document` を必要とするライブラリ(チャート、エディタ) |

## フレームワーク [#frameworks]

**React はそのまま動作します** — プロジェクトに `.tsx`/`.jsx` のアイランドが含まれた瞬間、Blume が自動的に有効化します。

[React Compiler](https://react.dev/learn/react-compiler) は React が有効なときは常にデフォルトでオンになっているため、アイランドは自動的にメモ化されます — 手書きの `useMemo`/`useCallback` は不要です。Blume に同梱されているので、インストールするものはありません。オプトアウトは `blume.config.ts` で行います:

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

**Vue と Svelte** もサポートされています。対応する Astro インテグレーションをインストールすれば、`.vue` または `.svelte` のアイランドを検出したときに Blume がレンダラーを接続します:

```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>
```

MDX で渡したプロパティ(`<Counter start={5} />`)はコンポーネントに転送され、子要素(`<Counter>label</Counter>`)はデフォルトスロットとして届きます。

:::tip
アイランドはクライアント側でハイドレートされるため、プロパティとして渡すものはシリアライズ可能でなければなりません — 文字列、数値、プレーンオブジェクトは可、関数は不可です。
:::

## フック [#hooks]

アイランドは独立してハイドレートされるため、プロジェクトのデータを引き回すための React コンテキストは存在しません。代わりに `blume/hooks` が、レイアウトがページ内にシリアライズした小さなスナップショットを読み取ります — props のバケツリレーは不要です:

```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>
  );
}
```

| フック | 戻り値 |
| --- | --- |
| `useBlume()` | サイトの `{ config, navigation }`、マウント前は `null` |
| `usePage()` | 現在のページの `{ route, title }`、マウント前は `null` |
| `useSearch()` | `{ search, results, loading }` — 設定済みの検索プロバイダーに問い合わせます |
| `useAskAI()` | `{ ask, messages, loading, reset }` — Ask AI エンドポイントからストリーミングします |

`useBlume()` と `usePage()` は、アイランドがマウントされるまで `null` を返します(サーバーとクライアントが同じ最初のフレームをレンダリングするため) — ガードを入れてください。このスナップショットは React を配信するページでのみ出力されるため、完全に静的なサイトでは一切コストがかかりません。

`PageLayout` で構築した [カスタムページ](/docs/advanced/custom-pages) では、そこにあるアイランドが読み取れるように `clientData` を渡します:

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