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

Astro 5.0 Experimental SSR で動的レンダリングを実装する【2026年最新】

Astro 5.0の実験的SSR機能を使った動的レンダリングの実装方法を解説。パフォーマンス最適化とSEO対策を両立する設定手順と実践コード例を紹介します。

Astro 5.0では実験的なSSR(Server-Side Rendering)機能が強化され、静的サイト生成(SSG)と動的レンダリングを柔軟に組み合わせられるようになりました。この記事では、Astro 5.0の実験的SSR機能を活用して、パフォーマンスとSEOを両立させた動的レンダリングの実装方法を解説します。

従来のAstroは静的サイト生成を得意としていましたが、ユーザー認証やリアルタイムデータ表示など、動的コンテンツが必要なケースでは制約がありました。Astro 5.0の実験的SSR機能は、この課題を解決しながらも、Astroの強みである高速なページロードを維持します。

Astro 5.0 実験的SSR機能の概要

Astro 5.0では、output: 'server'モードに加えて、実験的フラグexperimental.serverIslandsが導入されました。この機能により、ページの一部だけをサーバーサイドでレンダリングし、残りは静的に配信できます。

従来のSSRとの違い

従来のAstro SSRでは、ページ全体をサーバーサイドでレンダリングする必要がありました。これに対し、実験的SSR機能では以下の特徴があります。

  • 部分的なSSR: コンポーネント単位で動的レンダリングを指定可能
  • 静的部分のキャッシュ: 静的コンテンツはビルド時に生成され、CDNでキャッシュ可能
  • レスポンス速度の向上: 動的部分のみをサーバーで処理するため、TTFB(Time to First Byte)が改善
flowchart TD
    A[クライアントリクエスト] --> B{ルーティング判定}
    B -->|静的ページ| C[CDNから配信]
    B -->|動的コンポーネント含む| D[サーバーレンダリング]
    D --> E[静的部分をキャッシュから取得]
    D --> F[動的部分をサーバーで生成]
    E --> G[HTMLを結合]
    F --> G
    G --> H[クライアントに送信]
    C --> H

このフロー図は、Astro 5.0の実験的SSRにおけるリクエスト処理の仕組みを示しています。静的コンテンツはCDNから直接配信され、動的コンポーネントのみがサーバーで処理されます。

実験的SSR機能の有効化と基本設定

Astro 5.0で実験的SSR機能を使用するには、astro.config.mjsで設定を行います。

インストールと初期設定

まず、Astro 5.0以降のバージョンがインストールされていることを確認します。

npm create astro@latest my-ssr-project
cd my-ssr-project
npm install astro@^5.0.0

次に、astro.config.mjsで実験的SSR機能を有効化します。

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

export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone'
  }),
  experimental: {
    serverIslands: true
  }
});

アダプターの選択

Astro 5.0では、複数のデプロイ先に対応したアダプターが用意されています。

アダプター用途推奨環境
@astrojs/nodeNode.jsサーバーVPS、Docker、自己ホスティング
@astrojs/vercelVercel Edge FunctionsVercelデプロイ
@astrojs/cloudflareCloudflare WorkersCloudflare Pages
@astrojs/netlifyNetlify FunctionsNetlifyデプロイ

Vercelにデプロイする場合の設定例:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel({
    edgeMiddleware: true
  }),
  experimental: {
    serverIslands: true
  }
});

動的コンポーネントの実装パターン

実験的SSR機能では、server:deferディレクティブを使用して、特定のコンポーネントのみを動的にレンダリングできます。

ユーザー認証情報の表示

ログインユーザーの情報を動的に表示する例です。

---
// src/components/UserProfile.astro
export const prerender = false; // このコンポーネントは常にサーバーでレンダリング

const session = Astro.cookies.get('session');
let user = null;

if (session) {
  const response = await fetch(`${import.meta.env.API_URL}/user`, {
    headers: {
      'Authorization': `Bearer ${session.value}`
    }
  });
  user = await response.json();
}
---

{user ? (
  <div class="user-profile">
    <img src={user.avatar} alt={user.name} />
    <span>{user.name}</span>
  </div>
) : (
  <a href="/login">ログイン</a>
)}

このコンポーネントをページで使用する際は、server:deferを指定します。

---
// src/pages/index.astro
import UserProfile from '../components/UserProfile.astro';
---

<html>
  <head>
    <title>ホームページ</title>
  </head>
  <body>
    <header>
      <h1>静的コンテンツ</h1>
      <UserProfile server:defer />
    </header>
    <main>
      <!-- 静的コンテンツ -->
    </main>
  </body>
</html>

リアルタイムデータの表示

APIから取得したリアルタイムデータを表示する例です。

---
// src/components/LiveStats.astro
export const prerender = false;

const stats = await fetch(`${import.meta.env.API_URL}/stats`).then(r => r.json());
---

<div class="live-stats">
  <div class="stat">
    <span class="label">訪問者数</span>
    <span class="value">{stats.visitors}</span>
  </div>
  <div class="stat">
    <span class="label">アクティブユーザー</span>
    <span class="value">{stats.activeUsers}</span>
  </div>
  <div class="stat-updated">
    更新時刻: {new Date().toLocaleTimeString('ja-JP')}
  </div>
</div>

<style>
  .live-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1rem;
  }
  .stat {
    padding: 1rem;
    background: #f5f5f5;
    border-radius: 8px;
  }
  .label {
    display: block;
    font-size: 0.875rem;
    color: #666;
  }
  .value {
    display: block;
    font-size: 2rem;
    font-weight: bold;
  }
</style>

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

動的レンダリングを使用する際のパフォーマンス最適化手法を紹介します。

キャッシング戦略

サーバーサイドでレンダリングしたコンテンツも、適切にキャッシュすることで応答速度を向上できます。

// src/middleware.js
export async function onRequest({ request, locals }, next) {
  const url = new URL(request.url);
  
  // APIレスポンスのキャッシュ設定
  if (url.pathname.startsWith('/api/')) {
    const response = await next();
    response.headers.set('Cache-Control', 'public, max-age=60, s-maxage=300');
    return response;
  }
  
  return next();
}

データ取得の並列化

複数のAPIを呼び出す場合は、Promise.allを使用して並列処理します。

---
// src/components/Dashboard.astro
export const prerender = false;

const [user, notifications, activity] = await Promise.all([
  fetch(`${import.meta.env.API_URL}/user`).then(r => r.json()),
  fetch(`${import.meta.env.API_URL}/notifications`).then(r => r.json()),
  fetch(`${import.meta.env.API_URL}/activity`).then(r => r.json())
]);
---

<div class="dashboard">
  <section class="user-section">
    <h2>{user.name}</h2>
  </section>
  <section class="notifications">
    <h3>通知 ({notifications.length})</h3>
    <ul>
      {notifications.map(n => <li>{n.message}</li>)}
    </ul>
  </section>
  <section class="activity">
    <h3>最近のアクティビティ</h3>
    <ul>
      {activity.map(a => <li>{a.description}</li>)}
    </ul>
  </section>
</div>

ストリーミングレスポンスの活用

大量のデータを扱う場合は、ストリーミングレスポンスを使用してTTFBを改善できます。

// src/pages/api/stream.js
export async function GET() {
  const encoder = new TextEncoder();
  
  const stream = new ReadableStream({
    async start(controller) {
      // データを段階的に送信
      for (let i = 0; i < 100; i++) {
        const data = await fetchDataChunk(i);
        controller.enqueue(encoder.encode(JSON.stringify(data) + '\n'));
        await new Promise(resolve => setTimeout(resolve, 10));
      }
      controller.close();
    }
  });
  
  return new Response(stream, {
    headers: {
      'Content-Type': 'application/x-ndjson',
      'Cache-Control': 'no-cache'
    }
  });
}

SEO対策とメタタグ管理

動的レンダリングを使用する場合でも、SEOに配慮したメタタグ管理が重要です。

動的メタタグの設定

ユーザーごとに異なるメタタグを設定する例です。

---
// src/pages/profile/[id].astro
export const prerender = false;

const { id } = Astro.params;
const user = await fetch(`${import.meta.env.API_URL}/users/${id}`).then(r => r.json());

const title = `${user.name}のプロフィール`;
const description = user.bio || `${user.name}のプロフィールページです。`;
const ogImage = user.avatar || '/default-avatar.png';
---

<html>
  <head>
    <title>{title}</title>
    <meta name="description" content={description} />
    <meta property="og:title" content={title} />
    <meta property="og:description" content={description} />
    <meta property="og:image" content={ogImage} />
    <meta property="og:type" content="profile" />
    <meta name="twitter:card" content="summary_large_image" />
  </head>
  <body>
    <main>
      <h1>{user.name}</h1>
      <img src={user.avatar} alt={user.name} />
      <p>{user.bio}</p>
    </main>
  </body>
</html>

構造化データの動的生成

JSON-LD形式の構造化データも動的に生成できます。

---
// src/components/PersonSchema.astro
export interface Props {
  name: string;
  jobTitle: string;
  url: string;
  image: string;
}

const { name, jobTitle, url, image } = Astro.props;

const schema = {
  "@context": "https://schema.org",
  "@type": "Person",
  "name": name,
  "jobTitle": jobTitle,
  "url": url,
  "image": image
};
---

<script type="application/ld+json" set:html={JSON.stringify(schema)} />

エラーハンドリングとフォールバック

動的レンダリングでは、APIエラーやネットワーク障害に対する適切な処理が必要です。

エラー境界の実装

---
// src/components/SafeComponent.astro
export const prerender = false;

let data = null;
let error = null;

try {
  const response = await fetch(`${import.meta.env.API_URL}/data`);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }
  data = await response.json();
} catch (e) {
  error = e.message;
  console.error('データ取得エラー:', e);
}
---

{error ? (
  <div class="error-message">
    <p>データの取得に失敗しました。</p>
    <details>
      <summary>詳細</summary>
      <code>{error}</code>
    </details>
  </div>
) : data ? (
  <div class="content">
    {/* データ表示 */}
  </div>
) : (
  <div class="loading">読み込み中...</div>
)}

タイムアウト設定

長時間かかるAPI呼び出しにタイムアウトを設定します。

// src/utils/fetchWithTimeout.js
export async function fetchWithTimeout(url, options = {}, timeout = 5000) {
  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal
    });
    clearTimeout(id);
    return response;
  } catch (error) {
    clearTimeout(id);
    if (error.name === 'AbortError') {
      throw new Error('リクエストがタイムアウトしました');
    }
    throw error;
  }
}
---
import { fetchWithTimeout } from '../utils/fetchWithTimeout';

export const prerender = false;

let data = null;
try {
  data = await fetchWithTimeout(`${import.meta.env.API_URL}/slow-endpoint`, {}, 3000)
    .then(r => r.json());
} catch (e) {
  console.error(e);
}
---

デプロイとモニタリング

実験的SSR機能を本番環境で運用する際の注意点とモニタリング手法を紹介します。

環境変数の管理

# .env.production
API_URL=https://api.example.com
CACHE_TTL=300
MAX_REQUEST_TIMEOUT=5000
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel(),
  experimental: {
    serverIslands: true
  },
  vite: {
    define: {
      'import.meta.env.API_URL': JSON.stringify(process.env.API_URL)
    }
  }
});

パフォーマンスモニタリング

サーバーサイドのレスポンスタイムを計測する例です。

// src/middleware.js
export async function onRequest({ request, locals }, next) {
  const start = Date.now();
  const response = await next();
  const duration = Date.now() - start;
  
  response.headers.set('Server-Timing', `total;dur=${duration}`);
  
  // ログ出力(本番環境では適切なロギングサービスに送信)
  console.log({
    path: new URL(request.url).pathname,
    method: request.method,
    duration,
    status: response.status
  });
  
  return response;
}
sequenceDiagram
    participant C as クライアント
    participant M as Middleware
    participant S as Server Component
    participant A as External API
    participant L as Logger

    C->>M: HTTPリクエスト
    M->>M: タイマー開始
    M->>S: コンポーネント処理
    S->>A: API呼び出し
    A-->>S: APIレスポンス
    S-->>M: レンダリング完了
    M->>M: 処理時間計測
    M->>L: メトリクス送信
    M-->>C: HTTPレスポンス + Server-Timingヘッダー

このシーケンス図は、Astro 5.0のSSRにおけるリクエスト処理とパフォーマンス計測の流れを示しています。Middlewareでリクエストを受け取り、処理時間を計測してServer-Timingヘッダーとして返します。

まとめ

Astro 5.0の実験的SSR機能を活用することで、以下のメリットが得られます。

  • 柔軟なレンダリング戦略: 静的コンテンツと動的コンテンツを最適に組み合わせ可能
  • パフォーマンスの維持: 動的部分のみをサーバーで処理することでTTFBを最小化
  • SEO対策の両立: サーバーサイドレンダリングによりクローラーに完全なHTMLを提供
  • 段階的な導入: 既存のAstroプロジェクトに部分的に導入可能

実験的機能のため、本番環境での使用には十分なテストとモニタリングが必要ですが、静的サイトの高速性と動的コンテンツの柔軟性を両立できる強力な選択肢となります。

今後のAstroのアップデートで正式機能として安定化されることが期待されるため、早期に導入してフィードバックを提供することで、より良いフレームワークの発展に貢献できます。

参考リンク

#Astro #SSR #動的レンダリング #パフォーマンス最適化 #Web開発
シェア