T-CREATOR

Astro のレンダリング戦略を一望:MPA× 部分ハイドレーションの強みを図解解説

Astro のレンダリング戦略を一望:MPA× 部分ハイドレーションの強みを図解解説

Web 開発の世界では、パフォーマンスとユーザー体験の向上が常に求められています。近年注目を集めている Astro は、従来のフレームワークとは一線を画すアプローチで、これらの課題に挑戦しているのです。

本記事では、Astro がどのように MPA(Multi-Page Application)アーキテクチャと部分ハイドレーションを組み合わせることで、高速で効率的な Web サイトを実現しているのかを、図解を交えながら詳しく解説していきます。

背景

従来の Web フレームワークが抱える課題

React や Vue.js などのモダンなフレームワークは、SPA(Single Page Application)として動作するのが一般的でした。これらは豊かなインタラクティブ性を提供する一方で、初期ロード時に大量の JavaScript をダウンロードする必要があります。

結果として、特にモバイル環境や通信速度の遅い環境では、ページの表示速度が遅くなるという問題が生じていました。

パフォーマンス重視の時代へ

Google がページ速度をランキング要素に含めるようになり、Core Web Vitals が重要指標として定着した現在、不要な JavaScript を削減することが開発者の大きな関心事となっています。

このような背景の中で、Astro は「必要最小限の JavaScript だけを送る」という哲学を掲げて登場しました。

Astro の基本コンセプト

Astro は、デフォルトでゼロ JavaScript を目指すフレームワークです。つまり、静的なコンテンツは HTML と CSS だけで配信し、インタラクティブな部分にのみ JavaScript を適用するという考え方ですね。

この思想を実現するために、Astro は MPA アーキテクチャと部分ハイドレーションという 2 つの重要な戦略を採用しています。

以下の図は、Astro の基本的なアーキテクチャの全体像を示しています。

mermaidflowchart TB
    subgraph build["ビルド時"]
        astro["Astro コンパイラ"]
        static["静的HTML/CSS 生成"]
        island["Island 検出"]
    end

    subgraph runtime["実行時(ブラウザ)"]
        html["静的HTML配信"]
        hydrate["必要な部分のみ<br/>ハイドレーション"]
    end

    astro -->|コンパイル| static
    astro -->|分析| island
    static -->|配信| html
    island -->|JS最小化| hydrate

この図が示すように、Astro はビルド時と実行時で役割を明確に分離し、最適化を行っています。

課題

SPA の抱える JavaScript 肥大化問題

従来の SPA フレームワークでは、すべてのページロジックとコンポーネントをブラウザに送信する必要がありました。これは以下のような問題を引き起こします。

#問題点影響
1初期バンドルサイズの肥大化First Contentful Paint (FCP)の遅延
2不要な JavaScript の実行Time to Interactive (TTI)の悪化
3ハイドレーションコストページ表示後の応答性低下

特に問題なのは、静的なコンテンツ(ブログ記事やドキュメント)においても、フレームワーク全体のランタイムを読み込む必要があるという点でしょう。

フルページハイドレーションのオーバーヘッド

SPA では、サーバーサイドレンダリング(SSR)を使用する場合でも、ブラウザ側で全ページを再度「ハイドレーション」する必要があります。

ハイドレーションとは、サーバーで生成された静的 HTML に、JavaScript でイベントハンドラや状態管理を紐付ける処理のことです。この処理は、ページ全体に対して行われるため、非常に重いオーバーヘッドとなっていました。

以下の図は、従来の SPA におけるフルページハイドレーションの流れを示しています。

mermaidsequenceDiagram
    participant Browser as ブラウザ
    participant Server as サーバー
    participant App as アプリケーション

    Browser->>Server: ページリクエスト
    Server->>Browser: HTML(静的)
    Note over Browser: HTMLを表示<br/>(まだ操作不可)
    Browser->>Server: JSバンドル要求
    Server->>Browser: 全JSダウンロード
    Note over Browser: JS解析・実行
    Browser->>App: 全コンポーネント<br/>ハイドレーション
    Note over Browser: ようやく操作可能に

この図からわかるように、ユーザーが実際にページを操作できるようになるまでに、多くのステップと時間が必要でした。

静的コンテンツと動的コンテンツの混在

多くの Web サイトは、静的なコンテンツ(テキスト、画像)と動的なコンポーネント(検索フォーム、コメント機能)が混在しています。

しかし、従来のフレームワークでは、この 2 つを区別せず、すべてを JavaScript で管理していました。これは明らかに非効率的ですね。

解決策

MPA アーキテクチャの採用

Astro が採用する MPA(Multi-Page Application)アーキテクチャは、各ページが独立した HTML ファイルとして配信される伝統的な Web の仕組みです。

これは以下のような利点をもたらします。

#特徴メリット
1ページごとの独立性必要なリソースだけを読み込む
2サーバーサイド生成初回表示が高速
3ブラウザネイティブナビゲーションJavaScript ルーターが不要

MPA では、ページ遷移時にブラウザがネイティブに新しい HTML を読み込むため、フレームワークのルーティングライブラリが不要になります。

部分ハイドレーション(Partial Hydration)の実装

Astro の最大の特徴が、この部分ハイドレーションです。ページ全体をハイドレーションするのではなく、インタラクティブな部分だけを選択的にハイドレーションするという戦略ですね。

この仕組みは「Astro Islands」または「Component Islands」と呼ばれています。

Astro Islands の概念

Island アーキテクチャでは、ページを「静的な海」と「インタラクティブな島」に分けて考えます。

mermaidflowchart TB
    subgraph page["Webページ"]
        subgraph ocean["静的コンテンツ(海)"]
            header["ヘッダー(HTML/CSS)"]
            content["記事本文(HTML/CSS)"]
            footer["フッター(HTML/CSS)"]
        end

        subgraph islands["インタラクティブ島"]
            search["検索フォーム<br/>(JS有効)"]
            comment["コメント機能<br/>(JS有効)"]
            slider["画像スライダー<br/>(JS有効)"]
        end
    end

    ocean -.-> islands

この図が示すように、ページの大部分は静的な HTML/CSS で構成され、必要な部分だけが JavaScript で動作します。

ハイドレーションディレクティブ

Astro では、コンポーネントがいつハイドレーションされるかを細かく制御できます。

以下は主なディレクティブの一覧です。

#ディレクティブタイミング用途
1client:loadページロード直後即座に必要な UI
2client:idleブラウザがアイドル時重要だが緊急でない機能
3client:visible要素が表示された時スクロール先のコンテンツ
4client:mediaメディアクエリ一致時レスポンシブ機能
5client:onlyクライアントのみSSR 不要なコンポーネント

これらのディレクティブを使うことで、パフォーマンスを最大限に最適化できるのです。

マルチフレームワーク対応

Astro のもう一つの強みは、React、Vue、Svelte、Solid など、複数の UI フレームワークを同一プロジェクト内で使用できる点です。

これにより、既存のコンポーネントライブラリを活用しながら、段階的に Astro を導入することが可能になります。

mermaidflowchart LR
    astro["Astro プロジェクト"]

    subgraph components["コンポーネント"]
        react["React コンポーネント"]
        vue["Vue コンポーネント"]
        svelte["Svelte コンポーネント"]
        native["Astro ネイティブ"]
    end

    astro --> react
    astro --> vue
    astro --> svelte
    astro --> native

    react -->|部分ハイドレーション| output["最適化された<br/>HTML出力"]
    vue -->|部分ハイドレーション| output
    svelte -->|部分ハイドレーション| output
    native -->|静的生成| output

この柔軟性により、チームの技術スタックに合わせた開発が可能になりますね。

具体例

基本的な Astro プロジェクトのセットアップ

まずは、Astro プロジェクトを作成してみましょう。

以下のコマンドでプロジェクトを初期化します。

bash# Astroプロジェクトの作成
yarn create astro my-astro-site

# プロジェクトディレクトリに移動
cd my-astro-site

# 依存関係のインストール
yarn install

これで Astro の基本的なプロジェクト構造が作成されました。

静的ページの作成

次に、静的なコンテンツのみのページを作成します。これは JavaScript を一切含まない、純粋な HTML/CSS ページです。

以下は src​/​pages​/​index.astro の例です。

astro---
// フロントマター:ビルド時に実行されるコード
const title = "Astroで作る高速Webサイト";
const description = "MPAと部分ハイドレーションの力";
---

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title}</title>
    <meta name="description" content={description} />
  </head>
  <body>
    <main>
      <h1>{title}</h1>
      <p>{description}</p>
      <!-- 静的コンテンツはJavaScript不要 -->
      <article>
        <p>この部分は完全に静的なHTMLとして配信されます。</p>
        <p>ブラウザでJavaScriptがオフでも表示されます。</p>
      </article>
    </main>
  </body>
</html>

このコードでは、フロントマター(---で囲まれた部分)がビルド時に実行され、その下の HTML テンプレートが生成されます。

重要なのは、このページには一切の JavaScript が含まれないという点です。

React コンポーネントの統合と Island 化

次に、インタラクティブな機能が必要な部分に React コンポーネントを追加してみましょう。

まず、React 統合をインストールします。

bash# Reactインテグレーションの追加
yarn astro add react

これで React コンポーネントを Astro プロジェクトで使用できるようになりました。

次に、カウンターコンポーネントを作成します。

typescript// src/components/Counter.tsx
import { useState } from 'react';

interface CounterProps {
  initialCount?: number;
}

export default function Counter({
  initialCount = 0,
}: CounterProps) {
  // Reactの状態管理
  const [count, setCount] = useState(initialCount);

  return (
    <div className='counter'>
      <p>現在のカウント: {count}</p>
      {/* インタラクティブなボタン */}
      <button onClick={() => setCount(count + 1)}>
        増加
      </button>
      <button onClick={() => setCount(count - 1)}>
        減少
      </button>
      <button onClick={() => setCount(0)}>リセット</button>
    </div>
  );
}

このコンポーネントは、React の状態管理を使用したインタラクティブな UI です。

部分ハイドレーションの適用

作成した React コンポーネントを Astro ページに組み込み、部分ハイドレーションを適用します。

astro---
// src/pages/interactive.astro
import Counter from '../components/Counter';
---

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>部分ハイドレーションのデモ</title>
  </head>
  <body>
    <main>
      <h1>Astro Islands デモ</h1>

      <!-- 静的コンテンツ(JavaScript不要) -->
      <section>
        <h2>静的セクション</h2>
        <p>この部分はJavaScriptなしで表示されます。</p>
        <p>SEOにも最適化されています。</p>
      </section>

      <!-- Islandその1:即座にハイドレーション -->
      <section>
        <h2>重要なカウンター</h2>
        <Counter client:load initialCount={10} />
      </section>

      <!-- 静的コンテンツ -->
      <section>
        <h2>もう一つの静的セクション</h2>
        <p>静的コンテンツと動的コンテンツが共存しています。</p>
      </section>

      <!-- Islandその2:表示されたらハイドレーション -->
      <section>
        <h2>下部のカウンター</h2>
        <Counter client:visible initialCount={0} />
      </section>
    </main>
  </body>
</html>

このコードでは、2 つのカウンターコンポーネントが異なるハイドレーション戦略で配置されています。

client:load は即座にハイドレーションされ、client:visible はユーザーがスクロールして表示された時にハイドレーションされます。

ハイドレーション戦略の比較実装

異なるハイドレーション戦略を実際に比較してみましょう。

astro---
// src/pages/hydration-strategies.astro
import Counter from '../components/Counter';
---

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>ハイドレーション戦略の比較</title>
  </head>
  <body>
    <main>
      <h1>ハイドレーション戦略の違い</h1>

      <!-- 戦略1: ページロード直後 -->
      <section>
        <h2>client:load - ページロード直後</h2>
        <p>最も優先度の高いインタラクション用</p>
        <Counter client:load />
      </section>

      <!-- 戦略2: ブラウザがアイドル時 -->
      <section>
        <h2>client:idle - ブラウザアイドル時</h2>
        <p>重要だが緊急性の低い機能用</p>
        <Counter client:idle initialCount={5} />
      </section>

      <!-- 戦略3: 要素が表示された時 -->
      <section style="margin-top: 100vh;">
        <h2>client:visible - スクロール時</h2>
        <p>この要素はスクロールして表示されるまでハイドレーションされません</p>
        <Counter client:visible initialCount={100} />
      </section>

      <!-- 戦略4: メディアクエリ -->
      <section>
        <h2>client:media - モバイルのみ</h2>
        <p>画面幅が768px以下の時のみハイドレーション</p>
        <Counter client:media="(max-width: 768px)" />
      </section>
    </main>
  </body>
</html>

この実装により、それぞれの戦略がどのように動作するかを確認できます。

以下の図は、これらの戦略がいつ実行されるかを時系列で示しています。

mermaidsequenceDiagram
    participant User as ユーザー
    participant Browser as ブラウザ
    participant Load as client:load
    participant Idle as client:idle
    participant Visible as client:visible

    User->>Browser: ページアクセス
    Note over Browser: HTMLダウンロード<br/>静的コンテンツ表示

    Browser->>Load: 即座にハイドレーション
    Note over Load: JSダウンロード実行

    Browser->>Browser: メインスレッド処理
    Browser->>Idle: アイドル時にハイドレーション
    Note over Idle: 低優先度で実行

    User->>Browser: スクロール操作
    Browser->>Visible: 要素が表示されたら<br/>ハイドレーション
    Note over Visible: 必要になってから実行

この図から、それぞれの戦略が異なるタイミングで実行されることが理解できますね。

Vue コンポーネントとの併用

Astro では、React と Vue を同じページで使用することも可能です。

まず、Vue インテグレーションを追加します。

bash# Vueインテグレーションの追加
yarn astro add vue

次に、Vue コンポーネントを作成します。

vue<!-- src/components/SearchBox.vue -->
<script setup lang="ts">
import { ref, computed } from 'vue';

// 検索キーワードの状態
const searchQuery = ref('');
// サンプルデータ
const items = ref([
  'React',
  'Vue',
  'Svelte',
  'Solid',
  'Angular',
  'Preact',
  'Lit',
  'Alpine',
]);

// 検索結果のフィルタリング
const filteredItems = computed(() => {
  if (!searchQuery.value) return items.value;
  return items.value.filter((item) =>
    item
      .toLowerCase()
      .includes(searchQuery.value.toLowerCase())
  );
});
</script>

<template>
  <div class="search-box">
    <input
      v-model="searchQuery"
      type="text"
      placeholder="フレームワークを検索..."
      class="search-input"
    />
    <!-- 検索結果の表示 -->
    <ul class="results">
      <li v-for="item in filteredItems" :key="item">
        {{ item }}
      </li>
    </ul>
    <p class="count">{{ filteredItems.length }}件の結果</p>
  </div>
</template>

この Vue コンポーネントは、リアクタイブな検索機能を実装しています。

React と Vue の混在ページ

作成した React と Vue のコンポーネントを同じページで使用します。

astro---
// src/pages/multi-framework.astro
import Counter from '../components/Counter';
import SearchBox from '../components/SearchBox.vue';
---

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>マルチフレームワークデモ</title>
  </head>
  <body>
    <main>
      <h1>複数フレームワークの共存</h1>

      <!-- 静的コンテンツ -->
      <section>
        <h2>はじめに</h2>
        <p>このページでは、ReactとVueが同時に動作します。</p>
        <p>それぞれのコンポーネントは独立してハイドレーションされます。</p>
      </section>

      <!-- Reactコンポーネント -->
      <section>
        <h2>Reactコンポーネント</h2>
        <Counter client:idle initialCount={20} />
      </section>

      <!-- Vueコンポーネント -->
      <section>
        <h2>Vueコンポーネント</h2>
        <SearchBox client:visible />
      </section>

      <!-- 静的コンテンツ -->
      <section>
        <h2>パフォーマンスの利点</h2>
        <p>各フレームワークのランタイムは、</p>
        <p>必要なコンポーネントがある場合のみ読み込まれます。</p>
      </section>
    </main>
  </body>
</html>

この実装により、React と Vue が同じページ内で効率的に動作します。

重要なのは、各フレームワークのランタイムは、そのフレームワークを使用するコンポーネントが実際に配置されている場合にのみ読み込まれるという点です。

パフォーマンス測定とビルド出力の確認

実際にどれだけ JavaScript が削減されているか確認してみましょう。

bash# プロダクションビルドの実行
yarn build

ビルド後、dist​/​ ディレクトリに生成されたファイルを確認します。

bash# ビルド結果の確認
ls -lh dist/

以下は、典型的な Astro プロジェクトのビルド出力です。

#ファイルタイプサイズ説明
1HTML2-5 KB静的コンテンツ
2CSS5-10 KBスタイル
3JS(Island 用)10-30 KB必要な部分のみ
4画像・アセット可変最適化済み

従来の SPA と比較すると、初期ロードサイズが大幅に削減されていることがわかります。

以下の図は、従来の SPA と Astro のバンドルサイズを比較したものです。

mermaidflowchart LR
    subgraph spa["従来のSPA"]
        spa_fw["フレームワーク<br/>ランタイム<br/>(50-150KB)"]
        spa_app["アプリ<br/>コード<br/>(100-300KB)"]
        spa_libs["ライブラリ<br/>(50-200KB)"]
    end

    subgraph astro["Astro"]
        astro_static["静的HTML/CSS<br/>(5-15KB)"]
        astro_island["Island用JS<br/>(10-50KB)"]
    end

    spa_fw --> total_spa["合計: 200-650KB"]
    spa_app --> total_spa
    spa_libs --> total_spa

    astro_static --> total_astro["合計: 15-65KB"]
    astro_island --> total_astro

    style total_spa fill:#ffcccc
    style total_astro fill:#ccffcc

この図が示すように、Astro を使用することで、初期ロードサイズを大幅に削減できることがわかります。

実践的な例:ブログサイトの構築

最後に、実践的なブログサイトの例を見てみましょう。

まず、ブログ記事のレイアウトを作成します。

astro---
// src/layouts/BlogLayout.astro
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import TableOfContents from '../components/TableOfContents';
import ShareButtons from '../components/ShareButtons';

interface Props {
  title: string;
  description: string;
  publishDate: string;
}

const { title, description, publishDate } = Astro.props;
---

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>{title}</title>
    <meta name="description" content={description} />
  </head>
  <body>
    <!-- 静的ヘッダー(JavaScript不要) -->
    <Header />

    <main>
      <article>
        <!-- 記事メタ情報(静的) -->
        <header>
          <h1>{title}</h1>
          <time datetime={publishDate}>
            {new Date(publishDate).toLocaleDateString('ja-JP')}
          </time>
        </header>

        <!-- 目次(アイドル時にハイドレーション) -->
        <TableOfContents client:idle />

        <!-- 記事本文(静的) -->
        <div class="content">
          <slot />
        </div>

        <!-- シェアボタン(表示時にハイドレーション) -->
        <ShareButtons client:visible title={title} />
      </article>
    </main>

    <!-- 静的フッター -->
    <Footer />
  </body>
</html>

この実装では、記事の大部分が静的 HTML として配信され、インタラクティブな部分(目次、シェアボタン)のみが選択的にハイドレーションされます。

ブログ記事ページでは、以下のようにレイアウトを使用します。

markdown---
// src/pages/blog/astro-introduction.md
layout: ../../layouts/BlogLayout.astro
title: "Astroのレンダリング戦略"
description: "MPAと部分ハイドレーションの解説"
publishDate: "2025-01-15"
---

# Astro とは

Astro は、コンテンツにフォーカスした高速な Web サイトを
構築するためのフレームワークです。

この記事は完全に静的な HTML として配信されますが、
必要な部分だけがインタラクティブに動作します。

このマークダウンファイルは、ビルド時に静的 HTML に変換され、最小限の JavaScript のみが追加されます。

まとめ

Astro のレンダリング戦略は、MPA アーキテクチャと部分ハイドレーションを組み合わせることで、従来のフレームワークが抱えていた課題を解決しています。

ここで学んだ主要なポイントを振り返りましょう。

Astro の 3 つの核心的な強み

#特徴実現内容
1デフォルトゼロ JS静的コンテンツは HTML/CSS のみで配信
2部分ハイドレーション必要な部分だけ JS を適用
3マルチフレームワーク既存資産を活用可能

これらの特徴により、Astro は高速で SEO に強く、開発者体験も優れたフレームワークとなっています。

パフォーマンス面での優位性

従来の SPA と比較して、Astro は以下の点で優れたパフォーマンスを実現します。

初期ロード時間が大幅に短縮されることで、ユーザー体験が向上し、SEO ランキングも改善されるでしょう。Time to Interactive(TTI)も最小限に抑えられるため、ユーザーはすぐにページと対話できます。

また、不要な JavaScript の削減により、特にモバイル環境でのパフォーマンスが向上しますね。

適用すべきケース

Astro は、以下のようなプロジェクトに特に適しています。

コンテンツ中心の Web サイト(ブログ、ドキュメント、マーケティングサイト)では、静的コンテンツが主体でありながら、一部のインタラクティブ機能が必要な場合に最適です。

E コマースサイトでも、商品ページは静的に、カート機能は動的にという使い分けができます。

企業サイトやポートフォリオでは、SEO とパフォーマンスを重視しつつ、問い合わせフォームなどの動的要素を追加できます。

今後の展望

Astro は、現代の Web 開発における「パフォーマンスファースト」の思想を体現したフレームワークです。

JavaScript の使用を最小限に抑えながら、必要な部分では最新のフレームワークの力を借りるという柔軟なアプローチは、今後の Web 開発のスタンダードになる可能性を秘めています。

本記事で解説した概念と実装例を参考に、ぜひ Astro を使った高速な Web サイト構築にチャレンジしてみてください。

関連リンク