• #issue
  • #github-actions
  • #nuxt
  • #jleague

GitHub Actionsデプロイエラー - Jリーグクラブページ500エラー

ステータス

  • 未解決
  • 解決済み(2025-12-31)

発生日

2025-12-30 16:00 頃に初回発生

症状

GitHub ActionsでのNuxt generate(静的サイト生成)時に、特定のJリーグクラブページでランダムに500エラーが発生し、デプロイが失敗する。

エラーメッセージ

Errors prerendering:
├─ /financial-quiz/jleague/club/fcimabari/_payload.json (1077ms)
  ├── [500]
  ├── Linked from /financial-quiz/jleague/club/fcimabari
  ├── Linked from /financial-quiz/jleague/club/fcimabari?year=2024
├─ /financial-quiz/jleague/club/sagan/_payload.json (254ms)
  ├── [500]
  ├── Linked from /financial-quiz/jleague/club/sagan
  ├── Linked from /financial-quiz/jleague/club/sagan?year=2024

[error] Exiting due to prerender errors.
ELIFECYCLE  Command failed with exit code 1.

影響を受けるクラブ(ランダムに変わる)

  • fcimabari (FC今治) - 発生
  • sagan (サガン鳥栖) - 発生
  • ehimefc (愛媛FC)
  • parceiro (AC長野パルセイロ)

特徴

  1. ランダム性: 失敗するクラブが毎回異なる
  2. ローカルでは成功: ローカル環境の pnpm generate は1414ルートすべて成功
  3. データは正常: 該当クラブの2024年データはJSONに存在
  4. パラメータ付き: すべてのエラーURLに ?year=2024 が含まれている

失敗したGitHub Actions Run

Run ID日時失敗クラブ
205916066452025-12-30 08:00fcimabari, sagan
205909918092025-12-30 07:07ehimefc, sagan
205905807592025-12-30 06:48fcimabari, parceiro

原因(特定済み)

[club].vuegetClubUrl() 関数が ?year=2024 のようなクエリパラメータ付きURLを生成し、これが NuxtLink:to 属性に渡される。

// apps/web/app/pages/financial-quiz/jleague/club/[club].vue:380-397
const getClubUrl = (clubId: string) => {
  const query: Record<string, string> = {}
  const year = periodLabels.value[periodIndex.value]
  if (year) {
    query.year = year  // ← これがクエリパラメータを生成
  }
  // ...
  return `/financial-quiz/jleague/club/${clubId}${queryString}`
}

nuxt.config.tsnitro.prerender.crawlLinks: true により、Nitroのクローラーがこれらのクエリパラメータ付きURLも発見し、プリレンダリング対象として追加してしまう。

結果として:

  • 60クラブ × 複数年度分のクエリ付きURLが大量に生成される
  • CI環境で負荷が跳ね上がり、ランダムに500エラーが発生

試したこと

  1. ローカルでビルドし成功(1414ルート、エラーなし)
  2. データ存在確認しデータあり(fcimabari, sagan, ehimefc, parceiro すべて2024年データ存在)
  3. ALL_CLUBS_WITH_TIER 確認しクラブ定義あり

解決策(適用済み)

採用した対策: nitro.prerender.ignore でクエリパラメータ付きURLを除外

nuxt.config.ts に以下の設定を追加:

nitro: {
  preset: "cloudflare-pages-static",
  prerender: {
    crawlLinks: true,
    routes: ['/blog', '/'],
    concurrency: 5,
    ignore: [/\?/]  // クエリパラメータ付きURLをプリレンダリング対象から除外
  }
}

効果:

  • ベースURL(/financial-quiz/jleague/club/sagan)→ プリレンダリングされる ✓
  • クエリ付き(?year=2024)→ プリレンダリング対象外(CSR時は正常動作)
  • プリレンダリング対象URLが大幅に減り、CI環境での負荷とエラーリスクが低下

なぜクエリパラメータ付きURLをプリレンダリングしなくてよいのか

プリレンダリングのメリット(通常のURL)

  1. SEO: 検索エンジンのクローラーがJavaScriptを実行しなくてもコンテンツを読み取れる
  2. 初回表示速度: HTMLが事前生成されているため、アクセス時に即座にコンテンツが表示される

クエリパラメータ付きURL(?year=2024等)の場合

プリレンダリングする意味はほぼない:

  1. SEO的に不要: Googleは ?year=2024 を別ページとしてインデックスしない。ベースURL(/financial-quiz/jleague/club/sagan)がインデックスされれば十分
  2. ユーザー導線: 通常はベースURLからアクセスして、UI操作で年度を切り替える
  3. URL共有時の影響: クエリパラメータ付きURLを共有した場合、ベースのHTMLが返された後にクライアントサイドで年度が切り替わる(一瞬の切り替えがあるだけで実用上問題ない)

今回のignore設定の意味

ignore: [/\?/]  // クエリパラメータ付きURLを除外

これは本来プリレンダリングする必要がなかったURLを除外しただけであり、機能的なデメリットはない。むしろ不要なプリレンダリングを削減することでビルド時間短縮とCI安定性向上に寄与する。

クエリパラメータを使用している全箇所

ファイルパラメータ用途
financial-quiz/jleague/club/[club].vueyear, tab, chartTab年度・タブ状態の保持
japanese-writing-quiz/all.vuecorrect, totalクイズ結果の表示
japanese-writing-quiz/punctuation.vuecorrect, totalクイズ結果の表示
japanese-writing-quiz/modification-order.vuecorrect, totalクイズ結果の表示
japanese-writing-quiz/particles.vuecorrect, totalクイズ結果の表示

全て ignore: [/\?/] で除外されるが、問題なし:

  • ベースURLはプリレンダリングされる
  • クエリパラメータはクライアントサイドで処理される設計
  • SEO的にクエリ付きURLをインデックスする必要がない

検討したが採用しなかった対策

  1. crawlLinks: false にして routes を明示的に指定
    • 効果的だが新しいコンテンツ追加時にroutesの更新が必要になり運用負荷が高い
  2. concurrency をさらに下げる(2〜4)
    • 安定性は増すがビルド時間が長くなる。根本解決ではない
  3. デバッグログを出すDEBUG=nitro:* or NITRO_PRERENDER_LOG_LEVEL=debug
    • 原因特定には有効だが、根本解決ではない

関連ファイル

  • apps/web/app/pages/financial-quiz/jleague/club/[club].vue
  • apps/web/app/composables/jleague/useJLeagueData.ts
  • apps/web/data/j-league-data.json
  • .github/workflows/deploy.yml

適用結果(2025-12-31)

デプロイ成功

Run ID結果詳細
20608145403✅ 成功ignore設定適用後
20608097000❌ 失敗ignore設定適用前
20607978004❌ 失敗ignore設定適用前

ビルドログの確認

[info] [nitro] Prerendering 227 initial routes with crawler
[info] [nitro] Prerendered 1423 routes in 104.204 seconds
  • プリレンダリング: 1423 routes(エラーなし)
  • 所要時間: 104秒
  • ?year 付きURL: 0件(ignore設定により除外成功)

concurrency設定

ignore設定により負荷が下がったため、concurrency はデフォルト(10)に戻した。 明示的に指定しない場合、Nitroのデフォルト値10が適用される。

次のアクション

  • 根本原因を特定(crawlLinks + クエリパラメータ付きURL)
  • nitro.prerender.ignore でクエリパラメータ付きURLを除外
  • GitHub Actionsでデプロイが成功するか確認
  • デプロイ成功後、concurrency をデフォルトの10に戻す(ignore設定で負荷が下がったため)