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 では、コンポーネントがいつハイドレーションされるかを細かく制御できます。
以下は主なディレクティブの一覧です。
| # | ディレクティブ | タイミング | 用途 |
|---|---|---|---|
| 1 | client:load | ページロード直後 | 即座に必要な UI |
| 2 | client:idle | ブラウザがアイドル時 | 重要だが緊急でない機能 |
| 3 | client:visible | 要素が表示された時 | スクロール先のコンテンツ |
| 4 | client:media | メディアクエリ一致時 | レスポンシブ機能 |
| 5 | client: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 プロジェクトのビルド出力です。
| # | ファイルタイプ | サイズ | 説明 |
|---|---|---|---|
| 1 | HTML | 2-5 KB | 静的コンテンツ |
| 2 | CSS | 5-10 KB | スタイル |
| 3 | JS(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 サイト構築にチャレンジしてみてください。
関連リンク
articleAstro × 部分ハイドレーションの効果測定:TTI/INP に与えるインパクト検証
articleAstro でレイアウト崩れが起きる原因を特定する手順:スロット/スコープ/スタイル隔離
articleAstro のレンダリング戦略を一望:MPA× 部分ハイドレーションの強みを図解解説
articleAstro の 環境変数・秘密情報管理:.env とエッジ環境の安全設計
articleAstro で動的 OG 画像を生成する:Satori/Canvas 連携の実装レシピ
articleAstro の View Transitions 徹底解説:SPA 並みの滑らかなページ遷移を実装するコツ
articleZod 合成パターン早見表:`object/array/tuple/record/map/set/intersection` 実例集
articleバックアップ戦略の決定版:WordPress の世代管理/災害復旧の型
articleYarn 運用ベストプラクティス:lockfile 厳格化・frozen-lockfile・Bot 更新方針
articleWebSocket のペイロード比較:JSON・MessagePack・Protobuf の速度とコスト検証
articleWeb Components イベント設計チート:`CustomEvent`/`composed`/`bubbles` 実例集
articleWebRTC SDP 用語チートシート:m=・a=・bundle・rtcp-mux を 10 分で総復習
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来