• #nuxt
  • #cloudflare
  • #SSR
  • #SSG
  • #performance
  • #troubleshooting

ページ遷移が遅い問題:SSR vs SSG の選択ミス

問題の概要

本番環境(Cloudflare Pages)で、トップページ(/)からブログ一覧(/blog)へのページ遷移に2-3秒かかっていた。

ブログサイトとしては明らかに遅い。静的サイトなら50-200ms程度であるべき。


現状の構成(問題のある状態)

設定されていたもの

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: "cloudflare-pages"  // ← Cloudflare Workers上でSSR
  },
  content: {
    documentDriven: true,       // ← URLベースの動的コンテンツ取得
    database: {
      type: "sqlite"
    }
  }
})
# wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "nuxt-content-db"
database_id = "..."

設定されていなかったもの

// これが欠けていた
nitro: {
  prerender: {
    crawlLinks: true,
    routes: ['/blog', '/']
  }
}

なぜ遅いのか:リクエストフロー分析

現状(SSR + D1)

ユーザーがNuxtLinkをクリック
    ↓
Cloudflare Edge(最寄りのデータセンター)
    ↓
Cloudflare Worker起動(コールドスタート: 100-500ms)
    ↓
D1データベースにクエリ発行
    ↓
queryCollection('pages') 実行(500-1500ms)
    ↓
SSRでHTML生成(500-1000ms)
    ↓
クライアントに返却
    ↓
合計: 1.1秒 〜 3秒

あるべき姿(SSG)

ユーザーがNuxtLinkをクリック
    ↓
Cloudflare Edge(最寄りのデータセンター)
    ↓
事前生成済みのHTMLを返却(10-50ms)
    ↓
クライアントでハイドレーション
    ↓
合計: 50-200ms

なぜこの構成になったのか:5つの原因

1. Nuxt 3のデフォルト動作

Nuxt 3はデフォルトでSSRが有効。明示的に設定しないと静的生成されない。

// デフォルト(暗黙的に有効)
export default defineNuxtConfig({
  ssr: true  // これがデフォルト
})

静的生成するには明示的な設定が必要:

// 方法1: 全ページを静的化
export default defineNuxtConfig({
  nitro: {
    prerender: {
      crawlLinks: true
    }
  }
})

// 方法2: ルートごとに指定
export default defineNuxtConfig({
  routeRules: {
    '/**': { prerender: true }
  }
})

2. cloudflare-pages プリセットの誤解

このプリセット名が紛らわしい。

「Cloudflare Pages」と聞くと静的ホスティングをイメージするが、実際には:

プリセット実際の動作
cloudflare-pagesCloudflare Workers 上でSSRを実行
cloudflare同上(エイリアス)
cloudflare-moduleWorkers(ESModules形式)

つまり「Pages」という名前だが、動的なWorkers実行が前提

Cloudflare Pagesは以下の2つのモードを持つ:

  • 静的ファイル配信: ビルド成果物をそのまま配信
  • Functions (Workers): 動的処理をWorkers上で実行

cloudflare-pagesプリセットは後者を想定している。

3. D1データベースの設定

wrangler.tomlにD1が設定されていた:

[[d1_databases]]
binding = "DB"
database_name = "nuxt-content-db"

これはランタイムでのデータベースアクセスを想定した設定

Nuxt Content v3のドキュメントでは、D1を使った動的コンテンツ配信が紹介されているが、これは以下のようなユースケース向け:

  • 動的に更新されるコンテンツ(CMS連携)
  • ユーザーごとに異なるコンテンツ
  • APIルートでのデータベースアクセス

純粋なブログサイトには過剰な構成

4. documentDriven: true の影響

content: {
  documentDriven: true,  // これが有効
}

documentDrivenモードは:

  • URLに基づいてコンテンツを自動取得
  • [...slug].vueでのクエリを自動化
  • 動的なコンテンツ取得を前提としている

静的生成と併用可能だが、プリレンダリング設定がないとSSRになる。

5. Nuxt Content v3の新アーキテクチャ

Nuxt Content v3は大きくアーキテクチャが変わった:

項目v2v3
データストレージJSONファイルSQLite
クエリ実行メモリ内SQLクエリ
ビルド成果物静的JSON圧縮SQLiteダンプ
ランタイム(静的)なしWASM SQLite(ブラウザ内)
ランタイム(動的)なしD1 / LibSQL

v3のドキュメントやチュートリアルでは、D1との連携が強調されている。これがSSR構成を選択した理由の一つ。

しかし、公式ドキュメントによると:

Nuxt Content can be deployed to static hosting using Nuxt prerendering. Nuxt Content will load the database in the browser using WASM SQLite.

つまり静的生成でも問題なく動作する。


解決策

最小限の修正

nuxt.config.tsにプリレンダリング設定を追加:

export default defineNuxtConfig({
  nitro: {
    preset: "cloudflare-pages",
    prerender: {
      crawlLinks: true,    // リンクを辿って全ページを生成
      routes: ['/blog', '/']  // 確実に生成したいルート
    }
  },
  // ... 他の設定
})

D1データベースは必要か?

純粋なブログサイトなら不要

削除してもよい項目:

# wrangler.toml から削除可能
# [[d1_databases]]
# binding = "DB"
# database_name = "nuxt-content-db"
# database_id = "..."

ただし、以下の場合は残す:

  • APIルートでDBを使う予定がある
  • 動的なコンテンツ管理機能を追加する予定

ビルドコマンドの違い

# SSR用ビルド(現状)
pnpm build

# 静的生成(推奨)
pnpm generate  # または nuxt generate

pnpm buildでもプリレンダリング設定があれば静的ファイルが生成されるが、pnpm generateの方が意図が明確。


修正後の期待値

項目修正前修正後
ページ遷移時間2-3秒50-200ms
Workerコールドスタートありなし
D1クエリありなし(ビルド時に解決)
ランタイムコストWorkers実行分ほぼゼロ

教訓

1. プリセット名に惑わされない

cloudflare-pagesという名前でも動的SSR前提。静的サイトにするには明示的な設定が必要。

2. Nuxt 3はSSRがデフォルト

Nuxt 2時代のnuxt generateの感覚でいると静的生成されない。明示的にprerenderを設定する。

3. ブログサイトにD1は過剰

Nuxt Content v3がD1連携を推していても、純粋なブログなら静的生成で十分。WASM SQLiteがブラウザ内で動作するため、クライアントサイドナビゲーションも問題ない。

4. パフォーマンス計測の重要性

本番環境で実際にページ遷移してみないと問題に気づかない。Lighthouseだけでなく、実際のユーザー体験を確認すべき。


関連ドキュメント

参考リンク