アイランドアーキテクチャと部分ハイドレーション
ページ全体を巨大なSPAにせず、必要な部分だけJSを配ればいい。アイランド構成と部分ハイドレーションで初期JS量を削り、Resumabilityとの違いも原理から整理します。
- 1.アイランドアーキテクチャは静的HTMLの海に「動く島」だけを埋め込む構成。ページ全体を1つのコンポーネントツリーとしてハイドレートせず、島ごとに独立してJSを実行する。
- 2.部分ハイドレーションはさらに、島ごとの実行タイミングを可視化・操作イベント・アイドル時間などのトリガーで遅延させ、初期JS実行コストを削る。
- 3.Qwikのresumabilityはハイドレーション自体を不要にする別解。実行状態をHTMLにシリアライズし、初回イベント発火時にそのハンドラだけを取得して再開する。
なぜアイランド構成が必要になったのか
SPA/SSR/SSG のいずれを選んでも、サーバーで生成したHTMLをクライアントで「意味のある操作対象」に変える工程が要ります。これがハイドレーションです。従来型のSSRフレームワークは、ページ全体を1本のコンポーネントツリーとして扱い、サーバーが返したHTMLに対してクライアント側で同じツリーをもう一度構築し、仮想DOMを実HTMLへ突き合わせてイベントリスナーを結線します。
問題は、このハイドレーションがページ内の1要素でも動的な部分があれば、ツリー全体に対して発生することです。ブログ記事の「いいねボタン」1個のためだけに、記事本文・ナビゲーション・フッターまで含めた巨大なJSバンドルをダウンロードし、パースし、実行しなければなりません。静的な文字だけの部分にまでハイドレーションコストを払う――これがモノリシックなSSRの構造的な無駄です。
アイランドアーキテクチャは、この前提を覆します。ページの大部分は静的HTMLのままとし、インタラクティブな部分だけを独立した「島(island)」として個別にハイドレートするという構成です。
アイランドアーキテクチャの内部モデル
アイランド構成でのビルド・配信の流れは次のとおりです。
| 段階 | モノリシックSSR | アイランドアーキテクチャ |
|---|---|---|
| サーバー出力 | ページ全体を1つのアプリとしてレンダリング | 静的HTML + 島の位置にマーカー付きプレースホルダー |
| クライアントJS | ページ全体分のコンポーネントツリーとランタイム | 島ごとに独立した最小限のコンポーネント + ランタイム断片 |
| ハイドレーション単位 | ルート1つ(ツリー全体が対象) | 島単位(他の島や静的部分に影響しない) |
| JS未読み込み時の失敗範囲 | ページ全体が操作不能になりうる | その島だけが操作不能。周囲は最初から機能する静的HTML |
各島は互いに独立したコンポーネントツリーのルートであり、島同士でランタイムの状態やコンポーネントツリーを共有しません。したがって、ある島が壊れても別の島には波及せず、静的な地の文(ナビゲーション、記事本文、フッターなど)はそもそもJSに依存しないためハイドレーション不要です。この「静的な地」と「動く島」を明確に分離する発想は、Astroが採用するclient:ディレクティブ(client:load、client:visible、client:idleなど)に代表され、コンポーネント単位でハイドレーション方式を宣言的に指定します。
「島」は独立性の比喩です。海(静的HTML)はJSなしで完結し、島(インタラクティブUI)だけがJSという「動力源」を必要とします。島は互いに孤立しているため、1つの島のフレームワークやバージョンが他の島に影響しません。これは、異なるUIフレームワーク(React製の島とVue製の島など)を1ページに共存させる技術的根拠にもなっています。
部分ハイドレーションによるJS削減の仕組み
アイランドアーキテクチャが「どこをハイドレートするか」を扱う手法だとすれば、**部分ハイドレーション(partial hydration)**は「いつハイドレートするか」を扱う手法です。両者はしばしばセットで語られますが、独立した軸です。
部分ハイドレーションの核心は、島ごとのハイドレーション実行をトリガー条件まで遅延させることにあります。代表的なトリガーは次のとおりです。
| トリガー | 発火条件 | 典型的な用途 |
|---|---|---|
| 即時(eager) | ページロード直後 | ファーストビューで即操作が必要なUI(検索ボックス等) |
| 可視化(visible) | Intersection Observerで要素がビューポートに入った時 | 画面下部のコメント欄、カルーセル |
| アイドル(idle) | メインスレッドが空いた時(requestIdleCallback相当) | 優先度の低い装飾的インタラクション |
| 操作(interaction) | クリック・フォーカスなど、ユーザーが実際に触れた瞬間 | モーダルを開くボタン、アコーディオン |
| メディアクエリ | 特定のビューポート幅に一致した時 | モバイルのみで必要なハンバーガーメニュー |
これらのトリガーは内部的にIntersection Observerやアイドルコールバック、イベントリスナーの遅延登録として実装されます。重要なのは、トリガーが発火するまでその島のJSはネットワーク上でリクエストすらされない設計になっている点です。単に実行を遅らせるだけでなく、コード分割(コードスプリッティング)と組み合わせることで、初期ロード時にダウンロードされるJSバイト数そのものを減らします。
// 概念的な例:可視化トリガーによる部分ハイドレーション
const island = document.querySelector('[data-island="comment-widget"]');
const observer = new IntersectionObserver(async (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
// ここで初めて島のJSチャンクを取得・実行する
const { hydrate } = await import('./comment-widget.js');
hydrate(entry.target);
observer.unobserve(entry.target);
}
}
});
observer.observe(island);
この設計により、Core Web Vitals のうちTBT(Total Blocking Time)やINPに直結するメインスレッド占有時間を大きく削減できます。静的な地の文に対するJS実行コストがゼロになるため、記事本文が長いページほど効果が顕著です。ただし副作用として、defer/asyncと同様、島のスクリプト取得タイミングが遅延側に振れるほど「操作可能になるまでの遅延」がトレードオフとして生じます。トリガー選択は「初期表示速度」と「操作可能になるまでの体感遅延」のバランス設計です。
可視化・アイドル・操作トリガーを使う場合、JSが未実行の状態でユーザーがボタンを押す可能性があります。多くの実装は、静的HTMLの段階でもフォーム送信やリンク遷移などブラウザネイティブな動作にフォールバックできるようマークアップし、JS実行後により良いUX(ページ遷移なしの更新など)に置き換える「プログレッシブエンハンスメント」の考え方を踏襲します。操作トリガーの場合はイベントを一度キューに積み、ハイドレーション完了後に再生する実装もあります。
Resumability:ハイドレーションそのものを不要にする
部分ハイドレーションはJS実行の「量とタイミング」を最適化しますが、根本的な問題――サーバーで一度実行したロジックを、クライアントでもう一度実行してイベントを結線し直すという二重実行――は解消していません。この二重実行自体をなくす設計が、Qwikが採用する**Resumability(再開可能性)**です。
| 観点 | (部分)ハイドレーション | Resumability |
|---|---|---|
| クライアントでの再実行 | コンポーネントのロジックを再実行してツリーを再構築する | 再実行しない。中断していた地点から状態を復元して再開する |
| 初期JS実行 | 島の数・粒度に応じて発生する | 理論上ゼロに近づけられる(イベント発火まで実行しない) |
| 状態の受け渡し | 多くはpropsの再計算やツリー再構築で復元 | 実行時の状態をシリアライズしHTML内に埋め込む |
| JS取得の単位 | 島(コンポーネント)単位 | イベントハンドラ単位(さらに細粒度) |
Resumabilityでは、サーバーがコンポーネントを実行した時点の実行状態(どのイベントリスナーがどの要素に紐づくか、クロージャが何を参照しているか等)をシリアライズしてHTML内に埋め込みます。クライアントは起動時にこの状態を「再実行して構築」するのではなく、そのまま読み取って再開します。たとえばボタンのクリックハンドラは、ページロード時には一切のJSも紐づけられていません。ユーザーが実際にクリックした瞬間、そのイベントに対応するハンドラのコードチャンクだけがネットワークから取得され実行されます。
これは部分ハイドレーションの「操作トリガー」をさらに突き詰め、コンポーネント単位ですらなくハンドラ単位まで遅延の粒度を細かくしたアプローチと理解できます。ハイドレーションという概念(サーバーの出力をクライアントで再構築する処理)自体が存在しないため、原理的には初期実行コストをアイランドアーキテクチャよりさらに切り詰められます。ただし、状態のシリアライズ量が増える、実行順序に依存するロジックの設計が難しくなるといった新たな制約も生じるため、万能な解決策ではなく設計思想の異なる選択肢です。
アイランドアーキテクチャ、部分ハイドレーション、Resumabilityはいずれも手段が異なるだけで、目的は共通しています。「サーバーが返せる静的な部分はJSに依存させず、動的な部分だけに実行コストを限定する」ことです。SPA的な「全部JSで作る」発想からの揺り戻しとして、必要な箇所にだけ計算資源を配分する設計が近年の潮流になっています。
まとめ
アイランドアーキテクチャは、ページを静的HTMLの海と独立したインタラクティブな「島」に分割し、島単位でハイドレーションを完結させることで、無関係な静的部分へのJS実行コストをゼロにします。部分ハイドレーションはさらに一歩進め、可視化・アイドル・操作などのトリガーで各島の実行タイミング自体を遅延させ、初期JSバイト数とメインスレッド占有時間を削減します。Qwikのresumabilityは、ハイドレーションという「サーバー出力をクライアントで再実行する」工程そのものを廃し、実行状態をHTMLにシリアライズしてイベント発火時にハンドラ単位で再開する、より根本的な解法です。いずれも「必要な場所にだけJSを配る」という同じ目的地に向かう、異なる経路の設計です。
Web/フロントエンド Article
アイランドアーキテクチャと部分ハイドレーションを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
アイランドアーキテクチャ
比較で見る軸
難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 6
導入後に効く点
部分ハイドレーションはさらに、島ごとの実行タイミングを可視化・操作イベント・アイドル時間などのトリガーで遅延させ、初期JS実行コストを削る。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- Web/フロントエンド
- タグ数
- 6
判断チェックリスト
- 自社の用途が「アイランドアーキテクチャ / ハイドレーション」に近いか確認する。
- 強みである「アイランドアーキテクチャは静的HTMLの海に「動く島」だけを埋め込む構成。ページ全体を1つのコンポーネントツリーとしてハイドレートせず、島ごとに独立してJSを実行する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。