Invalid Date
Next.jsアプリをCloudflare Pagesにデプロイする悪夢:404エラーとの壮絶な戦い
はじめに
Next.jsアプリケーションをCloudflare Pagesにデプロイするのは、現代のウェブ開発において一般的なタスクであり、通常は比較的スムーズに進むはずです。しかし、今回、私たちは想像を絶するほど深く、そして多層的な404エラーの迷宮に迷い込むことになりました。ビルドは成功するのに、サイトは常に404。このブログ記事では、その壮絶な戦いの記録と、そこから得られた貴重な教訓を共有します。
最初の謎:ビルド成功なのに404
デプロイのたびに、Cloudflare Pagesは「Success: Your site was deployed!」と告げるにも関わらず、ブラウザには常に「404 Not Found」が表示されました。ローカルでは完璧に動作するアプリケーションが、なぜ本番環境で動かないのか?最初の容疑者は、Next.jsの多言語ルーティングを担うミドルウェアでした。
フェーズ1:型エラーの無限ループ
最初のビルドログには、src/app/api/forum/posts/[id]/comments/route.ts での型エラーが示されていました。
Type error: Route "src/app/api/forum/posts/[id]/comments/route.ts" has an invalid "GET" export:
Type "{ params: { id: string; }; }" is not a valid type for the function's second argument.
NextRequestからRequestへの変更、引数の分割代入の調整など、様々な型定義の修正を試みました。- 同時に、大量のESLint警告(
'error' is defined but never used.など)も表示されており、これらがビルドを妨げている可能性を疑い、一つずつ修正していきました。しかし、警告を修正しても根本の型エラーは消えず、新たな警告が生まれるという無限ループに陥りました。 - 最終的に、問題のファイルを一時的に空にすることで、エラーメッセージが
is not a moduleに変化。これにより、問題がこのファイル自体にあることを確認しました。 - そして、
post.commentsのcreatedAtがDateオブジェクトなのに、CommentSectionがstringを期待しているという、本当の型エラーを発見し、toISOString()で変換することでこれを解決しました。
このフェーズで学んだのは、ビルドエラーは必ずしも根本原因を示さないこと、そして警告の修正はデバッグの妨げになる場合があることでした。
フェーズ2:バージョン不整合の悪夢
型エラーを解決したと思いきや、新たなエラーが発生しました。
Error: Configuring Next.js via 'next.config.ts' is not supported. Please replace the file with 'next.config.js' or 'next.config.mjs'.
これは、Next.js 15から14へのダウングレードが原因でした。
next.config.tsをnext.config.mjsにリネーム。package.jsonのnextとeslint-config-nextのバージョンを14.2.5に、reactとreact-domを18.2.0にダウングレード。eslintのバージョンも^8.0.0に調整。geistフォントのインポート方法も、Next.js 14の仕様に合わせて修正しました。
このフェーズでは、Next.jsのメジャーバージョンアップが、依存関係や設定ファイルに広範囲な影響を与えることを痛感しました。
フェーズ3:データベースの旅(PrismaとCloudflare)
ビルドが通ったと思いきや、今度はランタイムでデータベースエラーが発生しました。
PrismaClientKnownRequestError: The table `main.Job` does not exist in the current database.
- 原因は、ローカルのSQLiteファイル (
dev.db) を本番環境で使おうとしていたことでした。サーバーレス環境では永続的なディスクがないため、これは不可能です。 - 最初の試みとして、Turso (SQLiteベースのサーバーレスDB) とPrismaのDriver Adapters (
previewFeatures = ["driverAdapters"]) を導入しました。 - しかし、
npx prisma db pushがP1012: the URL must start with the protocol file:というエラーで失敗。PrismaのDriver Adaptersは、まだマイグレーションツールをサポートしていないという、Prismaの仕様上の制約に直面しました。 - 次に、Prisma Data Proxy (Accelerate) を試み、
schema.prismaのproviderをpostgresqlに変更。しかし、今度はP1013: The provided database string is invalid.というエラーが発生。これは、providerがpostgresqlなのに、TursoのURLがlibsql://であるというプロトコル不整合でした。 DATABASE_URLとDIRECT_URLの使い分け、そして.envファイルの読み込み順序や構文の細かな問題にも直面しました。
このフェーズで、サーバーレス環境でのデータベース選択の重要性と、Prismaの特定の機能(Driver Adapters)の現在の制限を深く学びました。
フェーズ4:環境変数のサガ (NEXT_PUBLIC_BASE_URL)
データベースの問題を解決し、ローカルでは完璧に動作するようになったにも関わらず、Cloudflare Pagesにデプロイすると再び404エラー。
- ビルドログを詳細に確認すると、
TypeError: Failed to parse URL from ${cf_pages_url}/api/translateというエラーが。 NEXT_PUBLIC_BASE_URLにCF_PAGES_URLを設定しても、Cloudflareがこれを正しく展開せず、文字列の「${CF_PAGES_URL}」のままアプリケーションに渡していることが判明しました。- これにより、API呼び出しのURLが不正になり、サーバーサイドの
fetchが失敗していました。
最終的な解決策として、データベースを Neon (PostgreSQL) に変更しました。 NeonはPrismaのマイグレーションツールと完全に互換性があり、db push がスムーズに動作します。
そして、NEXT_PUBLIC_BASE_URL の問題に対しては、コード側で process.env.NEXT_PUBLIC_BASE_URL が文字列の「${CF_PAGES_URL}」である場合、http://localhost:3000 にフォールバックするように明示的に記述するワークアラウンドを適用しました。
最終的な勝利、そして新たな発見
これまでのすべての問題を乗り越え、ついにサイトが正常にデプロイされ、表示されるはずです。
この長い旅の途中で、Cloudflare Pagesのフレームワークプリセットが「なし」になっていたという、最も基本的な設定ミスも発見しました。これを「Next.js」に変更することで、Next.jsのサーバー機能がCloudflare上で正しく動作するようになりました。
まとめと教訓
このデバッグの旅は、予想以上に長く、困難なものでした。しかし、そこから得られた教訓は計り知れません。
- バージョン互換性: Next.js、React、Prisma、ESLintなど、主要なライブラリのバージョン間の互換性は常に注意深く確認すること。特にメジャーバージョンアップは慎重に。
- サーバーレスの制約: Cloudflare Pagesのようなサーバーレス環境では、ローカルファイルベースのデータベースは使用できない。永続的なデータにはクラウドデータベースが必須。
- Prismaの深い理解:
db pushやmigrateといったCLIツールと、アプリケーションのランタイムが使用するPrisma Clientの動作は異なる場合がある。特にDriver AdaptersやAccelerateのようなプレビュー機能を使う際は、ドキュメントを徹底的に読み込むこと。 - 環境変数の挙動: Cloudflare Pagesにおける環境変数の展開は、期待通りに動作しない場合がある。特に
CF_PAGES_URLのような組み込み変数を使用する際は、ビルドログでその値が正しく展開されているかを確認すること。 - デバッグの忍耐: 問題が解決しない場合、一度立ち止まり、最も基本的な部分(設定、環境)から疑い、一つずつ可能性を潰していく忍耐力が必要。
- ログの重要性: エラーメッセージは常に真実を語るが、その解釈は文脈に依存する。詳細なログはデバッグの生命線。
この経験は、私たちに多くのことを教えてくれました。そして、最終的にアプリケーションが動作する喜びは、この苦労を忘れさせてくれるほど大きなものです。