ページ遷移が遅い問題: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-pages | Cloudflare Workers 上でSSRを実行 |
cloudflare | 同上(エイリアス) |
cloudflare-module | Workers(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は大きくアーキテクチャが変わった:
| 項目 | v2 | v3 |
|---|---|---|
| データストレージ | 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だけでなく、実際のユーザー体験を確認すべき。
関連ドキュメント
- Nuxt Content v3:D1データベースは本当に必要なのか? - D1が実際には使われていなかった事実についての詳細解説