• #Cloudflare Pages
  • #Nuxt
  • #SSR
  • #トラブルシューティング
  • #デプロイ
  • #_routes.json
未分類

Cloudflare PagesでリロードするとNuxtページが404になる問題の調査と解決

問題の症状

Cloudflare Pagesにデプロイした動的ルートのNuxtページで以下の問題が発生:

  • 初回アクセス: ページが正常に表示される
  • F5でリロード: ERR_HTTP_RESPONSE_CODE_FAILURE 404 (Not Found) エラーが発生

例: https://log.eurekapu.com/2025-11-27/domain-transfer-onamae-to-cloudflare

ブラウザのコンソールには以下のエラーが表示:

GET https://log.eurekapu.com/2025-11-27/domain-transfer-onamae-to-cloudflare net::ERR_HTTP_RESPONSE_CODE_FAILURE 404 (Not Found)

原因の調査プロセス

当初の仮説(誤り)

Cloudflare PagesでNuxt 3のSSR(Server Side Rendering)アプリケーションを動作させる際のルーティング設定不足が原因と、最初は考えました。

なぜ初回は成功してリロードで失敗するのか?

  1. 初回アクセス(クライアントサイドナビゲーション):
    • Nuxtアプリがクライアントサイドで動作
    • Vue Routerがクライアント側でルーティングを処理
    • サーバーへのリクエストなしでページが表示される
  2. リロード時(サーバーサイドリクエスト):
    • ブラウザがサーバーに直接HTTPリクエストを送信
    • Cloudflare Pagesが適切にNitro関数にルーティングできていない
    • _routes.jsonの設定不足により404が返される

この仮説に基づき、nuxt.config.tsに以下の設定を追加しましたが、問題は解決しませんでした

nitro: {
  preset: "cloudflare-pages",
  prerender: {
    autoSubfolderIndex: false,
    crawlLinks: true,
    failOnError: false
  }
},
routeRules: {
  '/**': { ssr: true }
}

なぜ当初の仮説が誤りだったのか

Nuxt 3のcloudflare-pages presetはデフォルトでSSRモードで動作します。つまり:

  • nuxt.config.tsの設定変更は不要だった
  • routeRulesを明示的に設定しなくても、すべてのルートは既にSSRとして扱われている
  • 問題の本質は、Nuxt/Nitroの設定ではなく、ビルド後の_routes.jsonファイルの内容だった

実際の原因(_routes.jsonの誤った除外ルール)

ビルド出力ディレクトリ(dist/)の_routes.jsonを確認したところ、以下の設定が見つかりました:

{
  "version": 1,
  "include": ["/*"],
  "exclude": [
    "/_nuxt/*",
    "/dump.pages.sql",
    "/favicon.ico",
    "/robots.txt",
    "/data/*",
    "/excel/*",
    "/images/*",
    "/__nuxt_content/*",
    "/20*/*"  // ← これが問題!
  ]
}

/20*/*パターンがコンテンツページを除外していました。

この除外ルールはscripts/fix-routes.mjsで定義されており、画像ファイル(/2025-11-27/image.pngなど)を静的ファイルとして扱う意図でしたが、コンテンツページ/2025-11-27/domain-transfer-onamae-to-cloudflare)も除外してしまっていました。

結果:

  • コンテンツページへのリクエストが_worker.js(SSR関数)にルーティングされない
  • 対応する静的HTMLファイルも存在しない
  • 404エラーが発生

解決方法(検討中)

scripts/fix-routes.mjsから/20*/*の除外ルールを削除:

修正前:

const routes = {
  version: 1,
  include: ["/*"],
  exclude: [
    "/_nuxt/*",
    "/dump.pages.sql",
    "/favicon.ico",
    "/robots.txt",
    "/data/*",
    "/excel/*",
    "/images/*",
    "/__nuxt_content/*",
    "/20*/*"  // ← 削除
  ]
};

修正後:

const routes = {
  version: 1,
  include: ["/*"],
  exclude: [
    "/_nuxt/*",
    "/dump.pages.sql",
    "/favicon.ico",
    "/robots.txt",
    "/data/*",
    "/excel/*",
    "/images/*",
    "/__nuxt_content/*"
    // /20*/* を削除
  ]
};

なぜこの修正で解決するのか

  1. 画像ファイルは既に静的ファイルとして処理されている
    • nuxt.config.tshooks['nitro:build:public-assets']で、コンテンツディレクトリの画像をpublic/ディレクトリにコピー済み
    • 画像ファイル(例:/2025-11-27/image.png)は既に静的ファイルとしてアクセス可能
    • /20*/*の除外ルールは不要
  2. コンテンツページはSSR関数で処理する必要がある
    • コンテンツページ(例:/2025-11-27/domain-transfer-onamae-to-cloudflare)は動的ルート
    • _worker.js(Nitro関数)でサーバーサイドレンダリングする必要がある
    • _routes.jsonexcludeリストから除外すれば、_worker.jsにルーティングされる

デプロイ手順

修正後、以下のコマンドで再ビルド・デプロイ:

cd apps/web

# ビルド(postbuildフックでfix-routes.mjsが自動実行される)
pnpm build

# Cloudflare Pagesにデプロイ
pnpm exec wrangler pages deploy

ビルド時の確認

ビルド完了後、dist/_routes.jsonを確認し、/20*/*が除外リストから削除されていることを確認:

# 確認コマンド(Windows)
cat apps/web/dist/_routes.json

# 確認コマンド(Linux/Mac)
cat apps/web/dist/_routes.json

期待される出力:

{
  "version": 1,
  "include": ["/*"],
  "exclude": [
    "/_nuxt/*",
    "/dump.pages.sql",
    "/favicon.ico",
    "/robots.txt",
    "/data/*",
    "/excel/*",
    "/images/*",
    "/__nuxt_content/*"
  ]
}

確認方法

デプロイ完了後:

  1. ブラウザで動的ルートにアクセス(例:https://log.eurekapu.com/2025-11-27/domain-transfer-onamae-to-cloudflare
  2. F5キーでページをリロード
  3. 404エラーが発生せず、ページが正常に表示されることを確認

検証結果

✅ 問題は解決しました(2025-11-27)

scripts/fix-routes.mjsから/20*/*の除外ルールを削除し、再ビルド・デプロイした結果:

  • 初回アクセス: ✅ 正常に表示
  • F5でリロード: ✅ 正常に表示(404エラーなし)

結論

問題の原因は_routes.json/20*/*除外ルールでした。このルールにより:

  • コンテンツページ(/2025-11-27/domain-transfer-onamae-to-cloudflare)が_worker.jsにルーティングされず
  • 対応する静的HTMLファイルも存在しないため404エラーが発生

除外ルールを削除することで、すべてのコンテンツページが正しくSSR関数(_worker.js)でレンダリングされるようになり、リロード時も正常に動作するようになりました。

関連情報

トラブルシューティング

この修正で問題が解決しない場合

  1. _routes.jsonの内容を確認
    cat apps/web/dist/_routes.json
    
    • /20*/*が本当に削除されているか確認
    • postbuildスクリプトが正しく実行されているか確認
  2. Cloudflare Pagesのデプロイログを確認
    • デプロイ時にエラーが発生していないか
    • _worker.jsが正しくデプロイされているか
  3. Cloudflare Pagesのキャッシュをクリア
    • Cloudflare Pagesダッシュボードからキャッシュをパージ
  4. ローカルでプレビュー
    pnpm preview
    
    • ローカル環境で本番ビルドをテストし、問題を再現できるか確認

デバッグのヒント

問題が発生している場合、以下を確認:

  • ブラウザの開発者ツール(Network タブ): 実際にどのリクエストが送信され、どのレスポンスが返されているか
  • Cloudflare Pagesのファンクションログ: _worker.jsが実行されているか、エラーが発生していないか
  • wrangler pages devでローカルテスト: 本番環境と同じ挙動を再現できるか

重要な学び

  • _routes.jsonがCloudflare Pagesのルーティングを制御している
    • include: SSR関数でレンダリングするパス
    • exclude: 静的ファイルとして配信するパス
  • ビルド後のファイルを確認する重要性: 設定ファイル(nuxt.config.ts)だけでなく、実際のビルド出力(dist/_routes.json)を確認することで、問題の根本原因を特定できる