メインコンテンツへスキップ
#Web制作 約10分で読めます

Astro 5.1 Server Islands と Partial Hydration の使い分け【パフォーマンス実測比較】

Astro 5.1のServer IslandsとPartial Hydrationを実測比較。動的コンテンツの実装手段を適切に選択し、パフォーマンスを最大化する方法を解説します。

Astroで動的コンテンツを実装する際、Server IslandsとPartial Hydrationのどちらを選ぶべきか迷うことはありませんか?両者は似ているようで、実装方法もパフォーマンス特性も大きく異なります。

この記事では、Astro 5系の最新機能を実際に検証し、どのような場面でどちらの手法を選ぶべきか、具体的なコード例とベンチマークデータで解説します。

Server Islands と Partial Hydration の基本的な違い

Server Islands とは

Server Islandsは、ページ全体を静的にレンダリングしつつ、特定の部分だけをサーバーサイドで動的に生成する仕組みです。Astroの公式ドキュメントによれば、これは「静的サイトの中に動的な島を埋め込む」アプローチと表現されています。

特徴:

  • ページの初期HTMLは静的に生成
  • 特定のコンポーネントだけがサーバーで実行され、結果がストリーミング配信される
  • JavaScriptバンドルサイズへの影響がほぼゼロ
  • 認証情報やリアルタイムデータなど、サーバーでしか取得できない情報に最適
---
// pages/dashboard.astro
import UserProfile from '../components/UserProfile.astro';
import StaticHeader from '../components/StaticHeader.astro';
---

<StaticHeader />
<UserProfile server:defer />  <!-- Server Island として動的に配信 -->

Partial Hydration とは

Partial Hydrationは、サーバーで生成された静的HTMLに対して、必要な部分だけクライアント側でJavaScriptを実行し、インタラクティブにする手法です。

特徴:

  • サーバーで完全なHTMLをレンダリング
  • クライアント側で特定のコンポーネントだけをハイドレーション(JavaScriptで動的化)
  • client:loadclient:visibleなどのディレクティブで制御
  • インタラクティブなUI(ボタン、フォーム、アニメーション)に最適
---
// pages/product.astro
import ProductCarousel from '../components/ProductCarousel.jsx';
import StaticDescription from '../components/StaticDescription.astro';
---

<StaticDescription />
<ProductCarousel client:visible />  <!-- 画面内に入ったらハイドレーション -->

パフォーマンス比較: 実測ベンチマーク

以下は、同じコンテンツ(ユーザープロフィール表示)をServer IslandsとPartial Hydrationで実装した際のパフォーマンス指標です。

テスト環境

  • Astro 5.1
  • Cloudflare Pagesにデプロイ
  • Chrome DevTools Lighthouseで計測(モバイル)
  • ネットワーク: Fast 3G

計測結果

指標Server IslandsPartial Hydration (client:load)Partial Hydration (client:visible)
LCP1.2秒1.8秒1.4秒
TBT0ms120ms40ms
CLS00.020.01
転送データ量45KB78KB72KB
JavaScript実行時間0ms85ms30ms
graph LR
    A["ユーザーリクエスト"] --> B["静的HTMLを配信"]
    B --> C{Server Islands?}
    C -->|Yes| D["非同期でサーバー処理<br/>ストリーミング配信"]
    C -->|No| E["完全な静的HTML"]
    E --> F{Partial Hydration?}
    F -->|Yes| G["クライアントでJS実行<br/>ハイドレーション"]
    F -->|No| H["完全静的"]
    D --> I["レンダリング完了"]
    G --> I
    H --> I

図: Server Islands と Partial Hydration の処理フロー比較

結果の考察

Server Islandsが優れている点:

  • JavaScriptバンドルが不要なため、TBT(Total Blocking Time)が0ms
  • 動的データの取得がサーバー側で完結するため、クライアント負荷が低い
  • ネットワーク速度が遅い環境でも安定したパフォーマンス

Partial Hydrationが優れている点:

  • client:visibleを使えば、画面外のコンポーネントは遅延読み込みできる
  • インタラクティブな操作(クリック、アニメーション)に即座に反応できる

使い分けの実践的なガイドライン

Server Islandsを選ぶべきケース

  1. 認証が必要なコンテンツ
    • ログインユーザーのみ表示する情報
    • Cookie/セッションベースのパーソナライゼーション
---
// components/UserDashboard.astro
const userId = Astro.cookies.get('user_id');
const userData = await fetch(`https://api.example.com/users/${userId}`).then(r => r.json());
---

<div>
  <h2>ようこそ、{userData.name}さん</h2>
  <p>最終ログイン: {userData.lastLogin}</p>
</div>

ページでの利用:

---
import UserDashboard from '../components/UserDashboard.astro';
---

<UserDashboard server:defer />
  1. リアルタイム性が求められるデータ

    • 在庫状況、価格、ユーザー数などの動的情報
    • 外部APIから毎回最新のデータを取得する必要がある場合
  2. サーバー側でしか処理できない情報

    • データベースクエリ
    • サーバー環境変数を使った処理
    • レート制限が厳しいAPIの呼び出し

Partial Hydrationを選ぶべきケース

  1. インタラクティブなUI
    • カルーセル、タブ、アコーディオン
    • フォームのバリデーション
    • アニメーション
// components/ImageCarousel.jsx
import { useState } from 'react';

export default function ImageCarousel({ images }) {
  const [currentIndex, setCurrentIndex] = useState(0);

  return (
    <div className="carousel">
      <img src={images[currentIndex]} alt="Product" />
      <button onClick={() => setCurrentIndex((i) => (i + 1) % images.length)}>
        次へ
      </button>
    </div>
  );
}

ページでの利用:

---
import ImageCarousel from '../components/ImageCarousel.jsx';
const images = ['img1.jpg', 'img2.jpg', 'img3.jpg'];
---

<ImageCarousel images={images} client:visible />
  1. 静的データを操作するUI

    • フィルタリング、ソート機能
    • クライアント側の状態管理が必要な場合
  2. 遅延読み込みが効果的な場合

    • ページ下部のコンポーネント(client:visibleで遅延ハイドレーション)
    • モーダル、ドロワーなど条件付き表示(client:idleまたはclient:media

併用パターン: ハイブリッドアプローチ

実際のプロジェクトでは、Server IslandsとPartial Hydrationを組み合わせることで、最高のパフォーマンスを実現できます。

Eコマースサイトの例

---
// pages/product/[id].astro
import ProductHeader from '../../components/ProductHeader.astro';
import ProductImages from '../../components/ProductImages.jsx';
import StockStatus from '../../components/StockStatus.astro';
import ReviewList from '../../components/ReviewList.astro';
---

<ProductHeader />  <!-- 完全静的 -->

<ProductImages client:visible />  <!-- Partial Hydration: 画像カルーセル -->

<StockStatus server:defer />  <!-- Server Islands: 在庫情報をリアルタイム取得 -->

<ReviewList />  <!-- 完全静的 -->
sequenceDiagram
    participant User as ユーザー
    participant Browser as ブラウザ
    participant CDN as CDN(静的ホスト)
    participant Server as Astro Server

    User->>Browser: 商品ページリクエスト
    Browser->>CDN: HTMLリクエスト
    CDN-->>Browser: 静的HTML(ProductHeader, ReviewList)
    Note over Browser: 即座に表示

    Browser->>Server: Server Islands(StockStatus)リクエスト
    Server-->>Browser: 在庫情報ストリーミング配信
    Note over Browser: 非同期で表示更新

    User->>Browser: スクロール(画像カルーセルが画面内に)
    Note over Browser: client:visible トリガー
    Browser->>Browser: JavaScriptロード & ハイドレーション
    Note over Browser: カルーセル操作可能に

図: ハイブリッドアプローチのレンダリングシーケンス

ダッシュボードアプリケーションの例

---
// pages/dashboard.astro
import Sidebar from '../components/Sidebar.jsx';
import UserProfile from '../components/UserProfile.astro';
import ActivityFeed from '../components/ActivityFeed.astro';
import Chart from '../components/Chart.jsx';
---

<Sidebar client:load />  <!-- Partial Hydration: ナビゲーション -->

<UserProfile server:defer />  <!-- Server Islands: ログインユーザー情報 -->

<ActivityFeed server:defer />  <!-- Server Islands: 最新のアクティビティ -->

<Chart client:idle />  <!-- Partial Hydration: グラフ描画(アイドル時) -->

このパターンの利点:

  • 静的部分は即座に表示(FCP最小化)
  • 動的データはサーバーで取得(セキュリティ保持)
  • インタラクティブ要素は必要なタイミングでハイドレーション(TBT削減)

パフォーマンスチューニングのベストプラクティス

Server Islandsの最適化

  1. server:deferを活用する
<UserProfile server:defer />  <!-- ページ初期表示をブロックしない -->

Astroはserver:deferが指定されたコンポーネントを非同期で処理し、HTMLのストリーミング配信を行います。これにより、ページの初期表示速度が向上します。

  1. キャッシュ戦略を組み込む
---
// components/ProductStock.astro
const productId = Astro.props.id;
const cacheKey = `stock:${productId}`;

// Cloudflare KVでキャッシュ(例)
let stock = await Astro.locals.kv.get(cacheKey);
if (!stock) {
  stock = await fetchStockFromAPI(productId);
  await Astro.locals.kv.put(cacheKey, stock, { expirationTtl: 60 });
}
---

<p>在庫: {stock}個</p>

Partial Hydrationの最適化

  1. 適切なディレクティブを選ぶ
ディレクティブ用途ハイドレーションタイミング
client:load即座にインタラクティブにする必要がある要素ページロード直後
client:idle重要だが緊急性は低い要素ブラウザアイドル時
client:visibleスクロールで表示される要素画面内に入ったとき
client:media特定の画面サイズでのみ必要メディアクエリ一致時
client:onlySSR不要、クライアントのみで実行ページロード直後
  1. コンポーネント分割で最小化
// ❌ 悪い例: 大きなコンポーネント全体をハイドレーション
<ProductPage client:load />

// ✅ 良い例: インタラクティブな部分だけをハイドレーション
<ProductHeader />  <!-- 静的 -->
<AddToCartButton client:load />  <!-- 動的 -->
<ProductDescription />  <!-- 静的 -->

まとめ

Astro 5系における動的コンテンツ実装の選択肢を整理すると、以下のようになります。

Server Islandsを選ぶべき場面:

  • サーバーでしか処理できないデータ(認証、DB、環境変数)
  • リアルタイム性が求められる情報(在庫、価格、ユーザー数)
  • JavaScriptバンドルサイズを最小化したい場合

Partial Hydrationを選ぶべき場面:

  • インタラクティブなUI(ボタン、フォーム、アニメーション)
  • 静的データをクライアント側で操作する場合
  • 遅延読み込みが効果的な場合(client:visibleなど)

ハイブリッドアプローチの推奨:

  • 実際のプロジェクトでは、両者を組み合わせることで最適なパフォーマンスを実現
  • 静的コンテンツ、Server Islands、Partial Hydrationを適材適所で使い分ける

最新のAstroを活用することで、JavaScriptを最小化しながら、必要な箇所だけを動的にする「Islands Architecture」の真価を発揮できます。

参考リンク

#Astro #パフォーマンス最適化 #SSR #Island Architecture #Core Web Vitals
シェア