コンテンツサイトに SPA は必要か――Astro と Islands Architecture

astrofrontendarchitectureframework

「とりあえず Next.js」で本当に良かったのか

コーポレートサイト、技術ブログ、ドキュメントサイト、LP。こうしたコンテンツ中心のサイトを構築するとき、フレームワークに何を選んでいるだろうか。

多くのチームが「とりあえず Next.js」を選ぶ。チーム内に React の経験者が多い、情報が豊富、採用実績も十分。選択としては合理的に見える。しかし、いざ運用を始めると次のような問題に直面する。

  • Lighthouse のパフォーマンススコアが 80 前後から上がらない
  • ほぼ静的なページなのに 200KB 以上の JavaScript バンドルが配信されている
  • ページ数の増加に伴い、ビルド時間が数分単位に膨らむ
  • SSG と SSR と ISR の使い分けに開発者が混乱する

こうした課題を個別の設定やチューニングで対処する方法もある。しかし、問題の根は「コンテンツサイトという用途に対して、SPA フレームワークを選択したことによる構造的なミスマッチ」にある。本記事では、この構造的ミスマッチを分析し、その解決策として Astro と Islands Architecture を位置づける。

コンテンツサイトに SPA が過剰な理由: 構造的ミスマッチの分析

SPA フレームワークは、インタラクティブな Web アプリケーションを効率よく構築するために設計されている。リアルタイムに状態が変化するダッシュボード、複雑なフォームウィザード、チャットアプリ。こうした用途では、クライアントサイドでの状態管理とルーティングが不可欠であり、フレームワークのランタイムを配信するコストに見合うリターンがある。

一方、コンテンツサイトのページ構成を考えると、状況は大きく異なる。典型的なブログ記事ページの構成要素を分解してみる。

  • ヘッダー・フッター: 静的 HTML で十分
  • 記事本文: マークダウンから生成された静的 HTML
  • 目次: 静的 HTML(アンカーリンク)
  • シェアボタン: 軽量な JavaScript で実装可能
  • コメント欄: インタラクティブだが、ページの一部分にすぎない

ページの 90% 以上が静的コンテンツであるにもかかわらず、SPA フレームワークはページ全体をハイドレーションする。これは、フレームワークのランタイム(React 18 時点で react + react-dom が約 42KB(gzip))をダウンロードし、仮想 DOM ツリーを構築し、イベントリスナーをアタッチする処理がページ全体に対して走ることを意味する。

Pages Router 時代の getStaticProps / getServerSideProps、あるいは App Router での Server Components / Client Components の使い分けにより、初期表示の高速化や不要なクライアント JS の削減は図れる。しかし、App Router でも Client Components にはハイドレーションのコストが残る。生成された HTML には依然として React のランタイムと各 Client Component の JavaScript バンドルが含まれ、ブラウザは静的な HTML を受け取った後、そのコンポーネントを JavaScript で再構築する――いわゆるフルページハイドレーション相当のコストが発生する。

この構造的ミスマッチがもたらすコストは 3 つある。

転送量の増加: フレームワークランタイム + コンポーネントバンドルが毎ページで配信される。コンテンツサイトではこの JavaScript の大部分が不要となる。

Time to Interactive の悪化: HTML の描画は速くても、ハイドレーション完了までユーザー操作を受け付けない期間が生じる。モバイル回線や低スペック端末で顕著に体感される。

複雑性の増大: Pages Router での SSG / SSR / ISR の使い分けや App Router での Server / Client 境界の設計、キャッシュ戦略の管理。コンテンツ配信という本来の目的に対して、アーキテクチャの複雑性が過剰になる。

問題の本質は明確で、「ほぼ静的なページに、動的アプリケーション向けのアーキテクチャを適用している」こと自体にある。

MPA への回帰と Islands Architecture という解答

Astro はこの構造的ミスマッチに対して、根本的に異なるアプローチを取る。デフォルトでゼロ JavaScript を出力する MPA(マルチページアプリケーション)フレームワークとして設計されている。

「デフォルトでゼロ JavaScript」とは、.astro コンポーネントで書かれたページがビルドされると、純粋な HTML と CSS だけが出力されることを意味する。フレームワークのランタイムは配信されない。前セクションで挙げた構造的ミスマッチの 3 つのコストが、設計レベルで解消される。

しかし、コンテンツサイトにもインタラクティブな要素は必要になる。検索フォーム、ダークモード切り替え、コメント欄、アナリティクスウィジェット。ここで Islands Architecture が登場する。

Islands Architecture は、ページの大部分を静的 HTML でレンダリングし、インタラクティブな部分だけを独立した「島(island)」としてハイドレーションするパターンとなる。各島は他の島と独立して読み込まれ、実行される。ページ全体のハイドレーションは発生しない。

Astro では client:* ディレクティブで島のハイドレーション戦略を制御する。

---
// src/pages/blog/[slug].astro
import Header from '../components/Header.astro';      // 静的 — JS なし
import ArticleBody from '../components/ArticleBody.astro'; // 静的 — JS なし
import SearchWidget from '../components/SearchWidget.jsx'; // React コンポーネント
import Comments from '../components/Comments.svelte';       // Svelte コンポーネント
---

<Header />
<ArticleBody />

<!-- ページ読み込み時に即座にハイドレーション -->
<SearchWidget client:load />

<!-- ビューポートに入ったときにハイドレーション -->
<Comments client:visible />

各ディレクティブの使い分けは以下のとおり。

ディレクティブハイドレーションタイミング用途の例
client:loadページ読み込み時に即座ナビゲーションメニュー、検索フォーム
client:idleブラウザがアイドル状態のときアナリティクス、非優先ウィジェット
client:visibleビューポートに入ったときコメント欄、フッター付近の要素
client:media={QUERY}メディアクエリ条件を満たしたときモバイル専用 UI
client:only={FRAMEWORK}クライアントのみでレンダリングSSR 非対応のコンポーネント

この設計により、「ページの 90% は静的 HTML、残り 10% のインタラクティブ部分だけ JavaScript を配信する」という、コンテンツサイトに最適な配信構成が自然に実現する。Astro の公式ドキュメントでは「40% 高速、90% 少ない JavaScript」と謳われている。

Server Islands: 動的コンテンツの遅延注入

Astro 5 で安定化した Server Islands は、Islands Architecture をサーバーサイドに拡張する仕組みとなる。server:defer ディレクティブを付与したコンポーネントは、静的な HTML ページシェルを CDN から即座に配信した後、動的コンテンツをサーバーから遅延取得して注入する。

なお、Server Islands が対応しているのは .astro コンポーネントのみである。React / Vue / Svelte などのフレームワークコンポーネントに server:defer を付与することはできないため、動的コンテンツを遅延注入したい箇所は .astro コンポーネントとして実装する必要がある。

---
// ユーザーごとにパーソナライズされたコンテンツを遅延レンダリング
import UserGreeting from '../components/UserGreeting.astro';
---

<!-- 静的なページシェルは CDN キャッシュ可能 -->
<main>
  <h1>ブログタイトル</h1>
  <article><!-- 静的コンテンツ --></article>

  <!-- パーソナライズされた挨拶を遅延注入 -->
  <UserGreeting server:defer>
    <p slot="fallback">読み込み中...</p>
  </UserGreeting>
</main>

静的ページのキャッシュ効率を維持しつつ、ユーザー固有のコンテンツを表示する。CDN キャッシュとパーソナライゼーションの両立という、従来は SSR でしか実現できなかった要件に対応できる。

「でも SPA 的な体験は欲しい」への回答

MPA に対する典型的な懸念は「ページ遷移時に画面が白くなる」「SPA のようなスムーズな体験が得られない」という点だろう。

Astro はこの課題に View Transitions API で対応している。<ViewTransitions /> コンポーネントを追加するだけで、ページ間遷移にクロスフェードやスライドアニメーションが適用される。ブラウザネイティブの API を活用するため、追加の JavaScript バンドルは最小限で済む。

ブラウザサポートについては、Chrome / Edge はすでに対応済みで、Firefox も対応を進めている。Safari は 2024 年後半から部分的なサポートが始まっている。Astro は未対応ブラウザでは View Transitions を自動的に無効化し、通常のページ遷移へのフォールバックを行うため、対応状況の差異を意識せずに導入できる。

---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---

<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>

もう一つの懸念は、フレームワーク固有のコンポーネント資産の再利用だろう。Astro は React、Vue、Svelte、SolidJS、Preact、Alpine.js を同一プロジェクト内で混在利用できる。既存の React コンポーネントライブラリをそのまま持ち込み、Astro の島として配置することが可能となる。チームのスキルセットやコンポーネント資産を活かしたまま移行できるのは、現実的な導入判断において大きなポイントになる。

フレームワークをまたぐコンポーネント間で状態を共有する必要がある場合は、Nano Stores が推奨されている。Nano Stores はフレームワーク非依存の状態管理ライブラリで、React の島と Svelte の島が同じストアを参照する、といった構成が実現できる。

フレームワーク比較: 同じサイトを作るなら何が違うか

具体的なシナリオで比較する。「LP + ブログ(50 記事)+ ドキュメント(30 ページ)」で構成されるコーポレートサイトを各フレームワークで構築した場合、何が変わるか。

バンドルサイズと配信効率

フレームワーク静的ページの JS 配信量備考
Astro0KB(島なし)〜コンポーネント依存(島使用時)デフォルトゼロ JS。島の規模によって増加
Next.js約 70〜200KB+(一般的な構成での目安)React ランタイム + フレームワークコード
Nuxt約 50〜150KB+(一般的な構成での目安)Vue ランタイム + フレームワークコード
Gatsby約 200KB+React 全体ハイドレーション
SvelteKit約 10〜30KBコンパイル時最適化で比較的軽量

Astro と SvelteKit はいずれも軽量だが、設計思想が異なる。SvelteKit は SPA アーキテクチャでコンパイル時最適化により軽量化を図る。Astro は MPA アーキテクチャでそもそも JavaScript を出力しない。コンテンツサイトでは後者のアプローチがより直接的に課題を解決する。

ビルド速度

Gatsby でビルド時間の肥大化に悩んだ経験があるチームは多い。40 ページ程度のサイトでも Gatsby では 2〜3 分かかるケースがあるのに対し、Astro では同規模で約 10 秒程度で完了する。ページ数が増えるほど差は顕著になる。

フレームワークロックイン

Next.js は React、Nuxt は Vue、SvelteKit は Svelte。いずれも特定の UI フレームワークに強く依存する。

Astro はこの点で独自の立ち位置にある。.astro コンポーネントはテンプレート構文だが、島として配置するインタラクティブ部分には任意のフレームワークを選択できる。React から Vue への移行を段階的に進めているチームにとって、両方のコンポーネントを共存させられるのは実用的なメリットとなる。

// astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import vue from '@astrojs/vue';
import svelte from '@astrojs/svelte';

export default defineConfig({
  integrations: [react(), vue(), svelte()]
});

筆者のチームでも Anemonet.BrandDesign のコーポレートサイト構築時に、コンテンツ配信のパフォーマンスを重視して Astro を採用した。既存の React コンポーネントを島として再利用しつつ、ページ本体は .astro コンポーネントで静的に出力する構成により、Lighthouse パフォーマンススコアの改善を確認できた。

部分的なインタラクティブ機能の実装方法

各フレームワークで「ページ内に検索フォームを 1 つ配置する」場合の設計を比較すると、アプローチの違いが明確になる。

フレームワーク実装方法ページ全体への影響
Astro検索コンポーネントに client:load を付与検索コンポーネント分の JS のみ配信
Next.js通常のコンポーネントとして実装ページ全体のハイドレーションに含まれる
Nuxt通常のコンポーネントとして実装ページ全体のハイドレーションに含まれる
SvelteKit通常のコンポーネントとして実装コンパイル時最適化で影響は限定的

Astro だけが「インタラクティブな部分のみ」にコストを限定できる。この粒度の制御こそが、Islands Architecture の本質的な価値となる。

Astro を選ぶべきとき、選ばないべきとき

ここまでの分析を基に、判断基準を整理する。

Astro が最適なケース

コンテンツとインタラクションの比率が 7:3 以上の場合(筆者の経験上の目安として)、Astro は有力な選択肢となる。具体的には以下のようなプロジェクトが該当する。

  • ブログ・技術メディア: 記事本文が主体で、インタラクティブ要素はコメント欄やシェアボタン程度
  • ドキュメントサイト: Starlight テーマにより、Cloudflare、Google、Microsoft なども採用している実績がある
  • コーポレートサイト・LP: 企業情報やプロダクト紹介など、情報発信が主目的
  • EC カタログ: 商品一覧の閲覧が中心で、カート機能は限定的
  • ポートフォリオ: 作品展示が主体

Astro が不向きなケース

以下のユースケースでは、SPA フレームワーク(Next.js、Nuxt、SvelteKit 等)の方が適切となる。

  • 管理画面・ダッシュボード: リアルタイムに更新されるデータ表示、複雑なフィルタリング・ソート
  • リアルタイムコラボレーションツール: WebSocket を多用する双方向通信
  • 複雑なフォーム主体のアプリケーション: マルチステップウィザード、バリデーション連鎖
  • チャット・メッセージングアプリ: 常時接続・リアルタイム更新が前提

判断に迷う場合は、「ページの主要コンテンツがサーバーで確定するか、クライアントで動的に変化するか」を基準にすると良い。前者なら Astro、後者なら SPA フレームワークが適合する。

注意点として、Astro の周辺ツール群は Next.js や Nuxt と比較するとまだ発展途上にある。サードパーティのプラグインやインテグレーションの選択肢は増えているが、ニッチな要件への対応は自前実装が必要になる場合もある。

補足: Astro 5 / 6 の進化と今後の展望

Astro は活発に開発が続いており、直近のアップデートでコンテンツ管理の機能が大きく強化されている。

Astro 5 系(安定版 5.18) では、Content Layer API が安定化した。ローカルのマークダウンファイルだけでなく、CMS やデータベースなどリモートソースからも型安全にコンテンツを取得できる。

たとえば、Zod スキーマによる型定義とコレクションクエリは次のように記述する。

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blogCollection = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    pubDate: z.date(),
    tags: z.array(z.string()).optional()
  })
});

export const collections = { blog: blogCollection };
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';

// 型安全なコレクション取得(スキーマと一致しないエントリはビルド時にエラー)
const allPosts = await getCollection('blog');
---

getCollection() / getEntry() による型安全なアクセスにより、ランタイムエラーのリスクが低減される。Sessions API やレスポンシブ画像の組み込みサポートも追加されている。

Astro 6 ベータ(2026 年 1 月 13 日リリース) では、Vite Environment API を活用した開発サーバーの再設計が行われている。CSP(Content Security Policy)サポートも追加予定で、セキュリティ要件の厳しいプロジェクトでの採用障壁が下がる。なお、Node.js 22 以上が必須となり、Astro.glob() が削除される破壊的変更を含む点には注意が必要となる。

2026 年 1 月 16 日には Cloudflare が Astro Technology Company を買収した。フレームワーク自体は MIT ライセンスのオープンソースとして継続が明言されており、デプロイ先も Cloudflare に限定されない。GitHub Stars 55,200 以上、NPM 週間ダウンロード 90 万以上というコミュニティ規模と合わせて、中長期的な採用リスクは低いと評価できる。ただし、Cloudflare プラットフォームとの統合が優先される可能性はあり、他のデプロイ先を使う場合の対応速度に差が生じうる点は留意しておきたい。

まとめ: 技術選定を「構造」から考え直す

「どのフレームワークが最も優れているか」という問いには意味がない。重要なのは「プロジェクトの性質に、どのアーキテクチャが構造的に合うか」という視点で選定することとなる。

コンテンツサイトへの SPA フレームワーク適用は、設定やチューニングで解決できない構造的なコストを内包する。MPA + Islands Architecture はその問いに対する構造的な回答として機能する。

自身のプロジェクトで技術選定を見直す場合、次のステップを試してほしい。

  1. 既存サイトのバンドル分析: Chrome DevTools の Coverage タブで、配信している JavaScript のうち実際に実行されている割合を確認する
  2. コンテンツ対インタラクション比率の算出: ページの構成要素を洗い出し、静的コンテンツとインタラクティブ要素の比率を見積もる
  3. Astro での PoC 作成: npm create astro@latest で既存サイトの 1 ページを再現し、バンドルサイズとパフォーマンスを比較する

Astro を採用した場合、次のステップとして Headless CMS(Contentful、Sanity、microCMS 等)との連携や、Content Collections を活用したコンテンツ管理ワークフローの整備を検討するとよい。Content Layer API を使えば CMS のスキーマと Astro の型定義を一致させたまま運用できる。こうした発展的な構成については、別稿で詳しく取り上げる予定とする。


参考リンク