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

Astro 5.0 Server Islands でコンポーネント単位の部分的SSRを実装する【2026年5月最新】

Astro 5.0のServer Islands機能を使い、静的サイト内の動的コンポーネントをSSRで部分的にレンダリングする実装方法を解説。パフォーマンスとSEOを両立させる新しいアーキテクチャパターンを実践例付きで紹介します。

Server Islands が解決する課題

静的サイトジェネレーター(SSG)で構築したサイトに動的コンテンツを追加しようとすると、従来は以下の3つの選択肢しかありませんでした。

  • クライアントサイドレンダリング(CSR): JavaScriptが読み込まれるまでコンテンツが表示されず、SEOにも悪影響
  • ページ全体をSSR化: 静的ページの高速性を失い、サーバー負荷が増加
  • 外部API呼び出し: ビルド時に取得できないデータはクライアント側で取得するしかない

Astro 5.0で正式導入されたServer Islands機能は、ページ全体は静的SSGのまま、必要なコンポーネントだけをサーバーサイドで動的にレンダリングすることで、この課題を解決します。

認証情報を含むコンテンツ、リアルタイム更新が必要なダッシュボード、パーソナライズドレコメンドなど、従来は全体をSSR化するしかなかった機能を、パフォーマンスを犠牲にせず実装できます。

Server Islands のアーキテクチャ

Server Islands は、従来のIsland Architectureをサーバーサイドに拡張した概念です。

flowchart TD
    A["クライアントリクエスト"] --> B["静的HTMLを即座に返す"]
    B --> C["ブラウザがHTMLをレンダリング"]
    C --> D["Server Islandのプレースホルダーを検出"]
    D --> E["バックエンドにコンポーネントデータをリクエスト"]
    E --> F["サーバーがリアルタイムでSSRレンダリング"]
    F --> G["動的コンテンツをDOMに挿入"]
    G --> H["完全なページ表示完了"]

Astro Server Islands のレンダリングフロー

従来のSSRとの違い

比較項目従来のSSRServer Islands
初回レンダリング速度ページ全体をサーバーで生成するため遅い静的HTMLを即座に返すため高速
サーバー負荷全リクエストでサーバー処理が必要動的コンポーネントのみ処理
キャッシュ戦略CDNキャッシュが困難静的部分は完全キャッシュ可能
SEO対策クロール時にサーバー負荷静的コンテンツは事前レンダリング済み

Server Islands の基本実装

プロジェクトのセットアップ

Astro 5.0以降では、Server Islands機能はデフォルトで有効化されています。以下のコマンドで最新のAstroプロジェクトを作成してください。

npm create astro@latest my-project
cd my-project
npm install

astro.config.mjsで出力モードをhybridまたはserverに設定します。

import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'hybrid', // hybrid または server
  adapter: node({ mode: 'standalone' }),
});

Server Island コンポーネントの作成

Server Islandとして動作するコンポーネントは、server:deferディレクティブで指定します。

src/components/UserStats.astro:

---
// このコンポーネントはサーバーサイドで動的にレンダリングされる
const response = await fetch('https://api.example.com/stats', {
  headers: {
    'Authorization': `Bearer ${import.meta.env.API_TOKEN}`
  }
});
const stats = await response.json();
---

<div class="user-stats">
  <h3>リアルタイム統計</h3>
  <p>アクティブユーザー: {stats.activeUsers}</p>
  <p>今日の売上: ¥{stats.todaySales.toLocaleString()}</p>
  <p>最終更新: {new Date().toLocaleTimeString('ja-JP')}</p>
</div>

ページでの使用

src/pages/dashboard.astro:

---
// ページ自体は静的にプリレンダリング
export const prerender = true;
import UserStats from '../components/UserStats.astro';
---

<html>
<head>
  <title>ダッシュボード</title>
</head>
<body>
  <h1>ダッシュボード</h1>
  
  <!-- 静的コンテンツ:ビルド時に生成 -->
  <section>
    <h2>お知らせ</h2>
    <p>システムメンテナンスは完了しました。</p>
  </section>

  <!-- Server Island:リクエスト時に動的生成 -->
  <UserStats server:defer />
  
  <!-- 再び静的コンテンツ -->
  <footer>
    <p>&copy; 2026 Example Corp.</p>
  </footer>
</body>
</html>

この実装により、ページの大部分は静的HTMLとしてCDNからキャッシュ配信されつつ、UserStatsコンポーネントだけがリクエストごとにサーバーで最新データを取得して表示されます。

実践的なユースケース

認証付きパーソナライズドコンテンツ

ユーザーごとに異なる情報を表示する場合、従来はページ全体をSSR化するか、クライアントサイドでJavaScript読み込み後に取得する必要がありました。Server Islandsを使えば、認証情報をサーバーサイドで安全に処理しつつ、初回表示速度を維持できます。

src/components/PersonalizedRecommendations.astro:

---
// Cookieから認証トークンを取得
const token = Astro.cookies.get('auth_token')?.value;

let recommendations = [];
if (token) {
  const res = await fetch(`https://api.example.com/recommendations`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  recommendations = await res.json();
}
---

{recommendations.length > 0 ? (
  <section class="recommendations">
    <h2>あなたへのおすすめ</h2>
    <ul>
      {recommendations.map(item => (
        <li>{item.title} - ¥{item.price}</li>
      ))}
    </ul>
  </section>
) : (
  <p>ログインするとおすすめ商品が表示されます</p>
)}

使用例:

---
export const prerender = true;
import PersonalizedRecommendations from '../components/PersonalizedRecommendations.astro';
---

<html>
<body>
  <h1>商品一覧</h1>
  
  <!-- 静的商品リスト -->
  <section>
    <!-- ... -->
  </section>
  
  <!-- ユーザーごとに動的なおすすめ -->
  <PersonalizedRecommendations server:defer />
</body>
</html>

A/Bテスト・機能フラグの実装

Server Islandsを使えば、ユーザーセグメントやリクエスト時刻に応じて異なるUIを出し分けることができます。

src/components/FeatureFlag.astro:

---
interface Props {
  flagName: string;
  children: any;
  fallback?: any;
}

const { flagName, children, fallback } = Astro.props;

// リクエストヘッダーからユーザーIDを取得
const userId = Astro.request.headers.get('X-User-Id');

// 機能フラグサービスに問い合わせ
const res = await fetch(`https://flags.example.com/evaluate`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ flagName, userId })
});

const { enabled } = await res.json();
---

{enabled ? children : fallback}

使用例:

<FeatureFlag flagName="new-checkout-flow" server:defer>
  <NewCheckoutButton />
  <Fragment slot="fallback">
    <OldCheckoutButton />
  </Fragment>
</FeatureFlag>

リアルタイム在庫状況の表示

ECサイトで在庫状況をリアルタイムで表示する場合も、Server Islandsが有効です。

src/components/StockStatus.astro:

---
interface Props {
  productId: string;
}

const { productId } = Astro.props;

const res = await fetch(`https://inventory.example.com/stock/${productId}`);
const { inStock, quantity } = await res.json();
---

<div class="stock-status">
  {inStock ? (
    <span class="in-stock">在庫あり(残り{quantity}点)</span>
  ) : (
    <span class="out-of-stock">在庫切れ</span>
  )}
</div>

商品ページでの使用:

---
export const prerender = true;
import StockStatus from '../components/StockStatus.astro';

const { id } = Astro.params;
// 商品情報はビルド時に静的取得
const product = await getProductData(id);
---

<article>
  <h1>{product.name}</h1>
  <img src={product.image} alt={product.name} />
  <p>{product.description}</p>
  
  <!-- 在庫状況だけリアルタイム -->
  <StockStatus productId={id} server:defer />
  
  <button>カートに追加</button>
</article>

パフォーマンス最適化のベストプラクティス

フォールバックUIの提供

Server Islandの読み込み中にローディング表示を出すことで、UXを向上できます。

<UserStats server:defer>
  <Fragment slot="fallback">
    <div class="skeleton-loader">
      <div class="skeleton-line"></div>
      <div class="skeleton-line"></div>
    </div>
  </Fragment>
</UserStats>

データ取得の並列化

複数のServer Islandsが存在する場合、それぞれが独立してサーバーにリクエストします。バックエンド側で適切にキャッシュ戦略を実装しておくことが重要です。

// バックエンドAPIの例(Node.js + Express)
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 60 }); // 60秒キャッシュ

app.get('/api/stats', async (req, res) => {
  const cacheKey = 'stats';
  const cached = cache.get(cacheKey);
  
  if (cached) {
    return res.json(cached);
  }
  
  const stats = await fetchStatsFromDatabase();
  cache.set(cacheKey, stats);
  res.json(stats);
});

CDNキャッシュの活用

静的部分は完全にCDNでキャッシュし、Server Island部分だけをオリジンサーバーで処理することで、サーバー負荷を最小化できます。

Cloudflare WorkersやVercel Edge Functionsと組み合わせることで、エッジロケーションでServer Islandを処理することも可能です。

// Cloudflare Workers の例
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    
    // 静的コンテンツはCDNキャッシュ
    if (url.pathname.startsWith('/static/')) {
      return env.ASSETS.fetch(request);
    }
    
    // Server Island コンポーネントのリクエスト
    if (url.pathname.startsWith('/_server-islands/')) {
      return handleServerIsland(request, env);
    }
    
    // その他のリクエスト
    return env.ASSETS.fetch(request);
  }
};

まとめ

Astro 5.0のServer Islands機能により、以下が実現できます。

  • パフォーマンスとリアルタイム性の両立: 静的サイトの高速性を保ちながら、必要な部分だけ動的レンダリング
  • 段階的な導入が可能: 既存の静的サイトに少しずつ動的機能を追加できる
  • サーバー負荷の最小化: ページ全体をSSRするのではなく、必要なコンポーネントだけ処理
  • SEO対策: 静的コンテンツは完全にプリレンダリングされるため、クローラーが即座にアクセス可能

認証情報を扱うコンテンツ、パーソナライズ、A/Bテスト、リアルタイム在庫表示など、従来は全体をSSR化するか妥協するしかなかったシナリオで、新しい選択肢を提供します。

参考リンク

#Astro #Server Islands #SSR #パフォーマンス最適化 #Island Architecture
シェア