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

Astro 5.1 On-Demand Rendering で動的コンテンツを効率化する実装ガイド【2026年5月最新】

Astro 5.1のOn-Demand Rendering機能を使い、リクエストごとに動的生成するページと静的ページを混在させる実装方法を解説。SSGとSSRの良いとこ取りでパフォーマンスとSEOを両立します。

Astro 5.1 On-Demand Rendering とは何か

Astro 5.1(2026年5月リリース)では、ページ単位で静的生成(SSG)と動的レンダリング(SSR)を切り替える On-Demand Rendering の機能が強化されました。

従来のフレームワークでは「全ページSSG」か「全ページSSR」の二択を迫られることが多く、動的コンテンツが一部だけ必要な場合でもサイト全体をSSRにする必要がありました。

Astro 5.1 では、export const prerender = false をページファイルに追加するだけで、そのページだけをリクエスト時にレンダリングするSSRモードに切り替えられます。その他のページはビルド時に静的生成され、CDNから高速配信されます。

この「良いとこ取り」のアプローチにより、以下が実現できます。

  • 静的コンテンツ(ブログ記事・ランディングページ)はSSGで配信し、LCPを最小化
  • 動的コンテンツ(ユーザーダッシュボード・ログイン後ページ)はSSRで生成し、SEOとパーソナライズを両立
  • ビルド時間を短縮(動的ページをビルド対象から除外)

この記事では、On-Demand Rendering の実装方法、ユースケース、パフォーマンス最適化のベストプラクティスを実例とともに解説します。

On-Demand Rendering の基本実装

プロジェクト構成例

src/
├── pages/
│   ├── index.astro          # 静的ページ(SSG)
│   ├── blog/
│   │   └── [slug].astro     # 静的ページ(SSG)
│   ├── dashboard/
│   │   └── index.astro      # 動的ページ(SSR)← export const prerender = false
│   └── api/
│       └── user.ts          # エンドポイント(SSR)← export const prerender = false
└── layouts/
    └── Layout.astro

SSRモードへの切り替え方法

動的ページの例(src/pages/dashboard/index.astro:

---
export const prerender = false; // この行でSSRモードに切り替え

import Layout from '../../layouts/Layout.astro';

// リクエストごとに実行される処理
const cookie = Astro.request.headers.get('cookie');
const userId = parseUserIdFromCookie(cookie);

// データベースから最新データを取得
const userData = await fetch(`https://api.example.com/users/${userId}`).then(r => r.json());
---

<Layout title="ダッシュボード">
  <h1>こんにちは、{userData.name}さん</h1>
  <p>最終ログイン: {new Date(userData.lastLogin).toLocaleString('ja-JP')}</p>
</Layout>

静的ページの例(src/pages/index.astro:

---
// prerender の指定がない場合、デフォルトで静的生成(SSG)
import Layout from '../layouts/Layout.astro';
---

<Layout title="ホーム">
  <h1>ようこそ</h1>
  <p>このページはビルド時に生成され、CDNから配信されます</p>
</Layout>

astro.config.mjs の設定

On-Demand Rendering を使うには、adapter(デプロイ先のサーバーレス環境)を設定する必要があります。

Vercel の例:

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

export default defineConfig({
  output: 'hybrid', // SSGとSSRを混在させる
  adapter: vercel(),
});

Cloudflare Pages の例:

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

export default defineConfig({
  output: 'hybrid',
  adapter: cloudflare(),
});

output の選択肢:

モード説明
'static'全ページSSG(デフォルト)
'server'全ページSSR(prerender = true で個別にSSG化)
'hybrid'デフォルトSSG、prerender = false で個別にSSR化

推奨: ほとんどのページが静的な場合は output: 'hybrid' を使い、動的ページだけ prerender = false を指定するのが効率的です。

ユースケース別実装パターン

ケース1: 認証が必要なダッシュボード

要件:

  • ログイン前のランディングページは静的生成(SEO対策)
  • ログイン後のダッシュボードは動的生成(ユーザーごとに異なるデータ表示)

実装:

---
// src/pages/dashboard.astro
export const prerender = false;

import Layout from '../layouts/Layout.astro';

// セッションCookieからユーザーIDを取得
const sessionToken = Astro.cookies.get('session')?.value;

if (!sessionToken) {
  return Astro.redirect('/login');
}

// JWTトークンを検証
const { userId } = await verifyToken(sessionToken);

// データベースから最新データを取得
const tasks = await db.query('SELECT * FROM tasks WHERE user_id = $1 ORDER BY created_at DESC', [userId]);
---

<Layout title="ダッシュボード">
  <h1>タスク一覧</h1>
  <ul>
    {tasks.map(task => (
      <li>{task.title} - {task.status}</li>
    ))}
  </ul>
</Layout>

この実装のメリット:

  • //features などの静的ページはCDNから即座に配信される(LCP 100ms未満)
  • /dashboard はリクエスト時に最新データを取得し、パーソナライズされたUIを表示
  • ビルド時にユーザー数分のページを生成する必要がない(ビルド時間短縮)

ケース2: 外部APIのデータを表示するページ

要件:

  • ブログ記事一覧は静的生成
  • 「急上昇中の記事」セクションは外部APIから取得(1時間ごとに更新)

実装:

---
// src/pages/trending.astro
export const prerender = false;

import Layout from '../layouts/Layout.astro';

// 外部APIから最新のトレンドデータを取得
const trendingPosts = await fetch('https://analytics.example.com/api/trending?period=1h', {
  headers: {
    'Authorization': `Bearer ${import.meta.env.ANALYTICS_API_KEY}`,
  },
}).then(r => r.json());
---

<Layout title="急上昇中の記事">
  <h1>いま読まれている記事</h1>
  <ul>
    {trendingPosts.map(post => (
      <li>
        <a href={`/blog/${post.slug}`}>{post.title}</a>
        <span>({post.views.toLocaleString()} view)</span>
      </li>
    ))}
  </ul>
  <p>データ更新日時: {new Date().toLocaleString('ja-JP')}</p>
</Layout>

この実装のメリット:

  • ビルド時にAPIキーを埋め込まずに済む(セキュリティ向上)
  • キャッシュ戦略をサーバーレス関数のレベルで制御可能(後述)

ケース3: A/Bテスト・パーソナライゼーション

要件:

  • ユーザーごとに異なるCTAボタンを表示
  • Cookieまたはヘッダー情報に基づいてバリアントを切り替え

実装:

---
// src/pages/pricing.astro
export const prerender = false;

import Layout from '../layouts/Layout.astro';
import CTAButtonA from '../components/CTAButtonA.astro';
import CTAButtonB from '../components/CTAButtonB.astro';

// A/Bテストのバリアント判定
const variant = Astro.cookies.get('ab_test_variant')?.value || (Math.random() > 0.5 ? 'A' : 'B');

// Cookieに保存(次回訪問時も同じバリアントを表示)
Astro.cookies.set('ab_test_variant', variant, {
  maxAge: 60 * 60 * 24 * 30, // 30日
  path: '/',
});
---

<Layout title="料金プラン">
  <h1>料金プラン</h1>
  {variant === 'A' ? <CTAButtonA /> : <CTAButtonB />}
</Layout>

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

1. Edge Caching の活用

動的ページでも、レスポンスに Cache-Control ヘッダーを付与することで、CDN(Vercel Edge、Cloudflare CDN)でキャッシュできます。

例: 1時間キャッシュする:

---
export const prerender = false;

Astro.response.headers.set('Cache-Control', 's-maxage=3600, stale-while-revalidate=86400');

const trendingPosts = await fetch('https://analytics.example.com/api/trending?period=1h').then(r => r.json());
---

<h1>急上昇中の記事</h1>
<!-- 省略 -->

s-maxage=3600: CDNは3600秒(1時間)キャッシュする
stale-while-revalidate=86400: キャッシュ期限切れ後も古いコンテンツを返しつつ、バックグラウンドで再検証

2. データ取得の並列化

複数のAPIを呼ぶ場合、Promise.all() で並列化し、待ち時間を短縮します。

非効率な例(直列実行):

const user = await fetch('/api/user').then(r => r.json());
const tasks = await fetch('/api/tasks').then(r => r.json());
const notifications = await fetch('/api/notifications').then(r => r.json());

効率的な例(並列実行):

const [user, tasks, notifications] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/tasks').then(r => r.json()),
  fetch('/api/notifications').then(r => r.json()),
]);

3. クライアント側の部分的ハイドレーション

動的ページでも、静的な部分は初期HTMLに含め、インタラクティブな部分だけJSでハイドレートすることで、Time to Interactive(TTI)を短縮できます。

例: 静的なヘッダー + 動的なタスクリスト:

---
export const prerender = false;

import Header from '../components/Header.astro'; // 静的コンポーネント
import TaskList from '../components/TaskList.jsx'; // Reactコンポーネント

const tasks = await fetchTasks();
---

<Layout>
  <Header /> <!-- ハイドレーション不要 -->
  <TaskList client:load tasks={tasks} /> <!-- クライアント側でハイドレート -->
</Layout>

ハイドレーションディレクティブの選択:

ディレクティブタイミング
client:loadページロード時(優先度高)
client:idleブラウザがアイドル状態になったとき
client:visible要素が画面に入ったとき(Intersection Observer)
client:mediaメディアクエリが一致したとき
client:onlyサーバー側でレンダリングせず、クライアント側のみ

推奨: ファーストビューに不要なUIは client:visible で遅延ロードし、初期JSバンドルを削減します。

4. ISR(Incremental Static Regeneration)風の実装

AstroにはNext.jsのような組み込みISR機能はありませんが、CDNキャッシュ + バックグラウンド再検証で同等の挙動を実現できます。

例: 5分ごとに再検証:

---
export const prerender = false;

Astro.response.headers.set('Cache-Control', 's-maxage=300, stale-while-revalidate=3600');

const posts = await fetch('https://cms.example.com/api/posts').then(r => r.json());
---

<h1>最新記事</h1>
{posts.map(post => <article>{post.title}</article>)}

この設定の挙動:

  1. 最初のリクエスト時、サーバーレス関数が実行され、CDNにキャッシュされる
  2. 5分以内のリクエストは、CDNから即座にレスポンスが返る
  3. 5分経過後の最初のリクエストでは、古いキャッシュを返しつつ、バックグラウンドでサーバーレス関数を再実行
  4. 次のリクエストから新しいキャッシュが配信される

この方式により、ユーザーは常に高速なレスポンスを受け取りつつ、コンテンツは定期的に更新されます。

On-Demand Rendering のワークフロー図

flowchart TD
    A[クライアントがページをリクエスト] --> B{ページの種類は?}
    B -->|静的ページ<br/>prerender未指定| C[CDNからHTMLを配信<br/>LCP < 100ms]
    B -->|動的ページ<br/>prerender = false| D[サーバーレス関数を実行]
    D --> E{CDNキャッシュは有効?}
    E -->|Yes<br/>Cache-Control設定あり| F[CDNにキャッシュを保存]
    E -->|No| G[毎回サーバーレス関数実行]
    F --> H[次回以降はCDNから配信]
    G --> I[レスポンス返却]
    H --> I
    C --> I

図の解説:

  • 静的ページ(prerender 指定なし)はビルド時に生成され、CDNから配信されるため、LCPは100ms未満
  • 動的ページ(prerender = false)はリクエストごとにサーバーレス関数が実行される
  • Cache-Control ヘッダーを設定すると、動的ページもCDNでキャッシュされ、次回以降は高速配信される

注意点とトラブルシューティング

1. adapter 未設定のエラー

エラーメッセージ:

Error: Cannot use `prerender: false` without an adapter.

解決策: astro.config.mjs に adapter を追加してください(前述の設定例を参照)。

2. 環境変数がサーバーレス関数で使えない

原因: ビルド時の環境変数(PUBLIC_ プレフィックス)とランタイムの環境変数は異なります。

解決策: デプロイ先のプラットフォーム(Vercel、Cloudflare Pages)で環境変数を設定し、import.meta.env で参照します。

Vercel の例:

const apiKey = import.meta.env.API_KEY; // Vercelのダッシュボードで設定

3. ビルド時間が長い

原因: output: 'server' にすると、すべてのページがビルド対象から除外され、逆に動的ページが多すぎるとビルドが遅くなります。

解決策:

  • output: 'hybrid' を使い、静的ページはビルド時に生成する
  • 動的ページの数を最小限にし、データ取得をAPIエンドポイント化する

4. SEOへの影響

懸念: 動的ページは初期HTMLにコンテンツが含まれるか?

回答: 含まれます。Astro の SSR はサーバー側でHTMLを生成するため、検索エンジンのクローラーは完全なHTMLを受け取ります。これはクライアント側レンダリング(CSR)と異なる点です。

検証方法:

curl https://yoursite.com/dashboard | grep "<h1>"

サーバー側でレンダリングされている場合、<h1> タグが含まれたHTMLが返ります。

まとめ

Astro 5.1 の On-Demand Rendering により、以下が実現できます。

  • 静的ページと動的ページを混在させ、パフォーマンスとSEOを両立
  • export const prerender = false の一行で簡単に切り替え可能
  • CDNキャッシュと組み合わせることで、ISR風の挙動を実現
  • 認証、A/Bテスト、外部API連携など、幅広いユースケースに対応

ポイント:

  • ほとんどのページが静的な場合は output: 'hybrid' を使う
  • 動的ページにも Cache-Control ヘッダーを設定し、CDNキャッシュを活用する
  • データ取得は並列化し、サーバーレス関数の実行時間を短縮する
  • 初期HTMLに静的コンテンツを含め、インタラクティブな部分だけクライアント側でハイドレートする

この機能により、従来は「全ページSSR」にせざるを得なかったサイトも、ページ単位で最適なレンダリング方式を選択できるようになりました。

参考リンク

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