コンテンツにスキップ
Blume is now publicly available.
Blume
日本語
Esc
移動開く⌘Jプレビュー
このページの内容

アイランド

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

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

islands/ の規約

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

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Clicked {count}</button>;
}
Here's a live counter: <Counter />

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

components.ts でのアイランド登録

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

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

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

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

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

ハイドレーション

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

// 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 を必要とするライブラリ(チャート、エディタ)

フレームワーク

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

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

export default defineConfig({
  react: { compiler: false },
});

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

# Vue
npm install @astrojs/vue vue

# Svelte
npm install @astrojs/svelte svelte
<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>)はデフォルトスロットとして届きます。

フック

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

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 で構築した カスタムページ では、そこにあるアイランドが読み取れるように clientData を渡します:

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

このページは役に立ちましたか?