Invalid Date
Next.js + NextAuth + Cloudflare Pages: Facebookログイン実装とデプロイの壮大なデバッグ記録
はじめに
このブログ記事は、Next.jsのApp Router、NextAuth.js、Prisma、そしてCloudflare Pagesを組み合わせたプロジェクトで、Facebookログイン機能を実装し、本番環境にデプロイするまでの、長く困難なデバッグの旅を記録したものです。一見シンプルな機能追加に見えましたが、環境間の差異、フレームワークの特性、そして外部サービスの複雑な設定が絡み合い、多岐にわたる問題に直面しました。
フェーズ1: Facebookログインの初期設定とNextAuthのエラー
課題1: Invalid Scopes: email エラー
- 問題: Facebookログインを試みると、「このURLのドメインはアプリのドメインに含まれていません」というエラーが発生。
- 原因: Facebookアプリの設定で、サイトのドメインが許可されていなかった。また、
emailスコープがFacebookアプリ側で許可されていなかった。 - 解決:
- Facebookアプリの「アプリドメイン」に本番ドメイン(
konnichiwork.prosalmontech.org)を追加。 - 「有効なOAuthリダイレクトURI」に
https://konnichiwork.prosalmontech.org/api/auth/callback/facebookを追加。 - 最大の原因: アプリのタイプが「ビジネス」に設定されていたため、
emailスコープの利用に「ビジネス認証」が必要だった。 - 最終解決: 新しいFacebookアプリを「ビジネス」以外のタイプ(「なし」または「消費者」)で作成し直し、そのアプリで設定を再構成。
- Facebookアプリの「アプリドメイン」に本番ドメイン(
課題2: TypeError: immutable (NextAuthコールバック)
- 問題: Facebook認証後、
next-authのコールバック処理中にTypeError: immutableが発生し、サーバーエラーになる。 - 原因:
next-authv5のjwtおよびsessionコールバック内で、tokenやsession.userオブジェクトを直接変更しようとしていたため。これらのオブジェクトは不変(immutable)である。 - 解決:
jwtおよびsessionコールバック内で、tokenやsession.userを直接変更するのではなく、スプレッド構文(...)を使って新しいオブジェクトを生成し、それを返すように修正。
課題3: Argument 'password' is missing (Prisma検証)
- 問題: Facebookログインで新規ユーザーを作成しようとすると、Prismaが
passwordフィールドの欠落を指摘してエラーになる。 - 原因:
prisma/schema.prismaのUserモデルでpasswordフィールドが必須(String)になっていたが、OAuthログインではパスワードが提供されないため。 - 解決:
Userモデルのpasswordフィールドをオプショナル(String?)に変更し、npx prisma migrate devでデータベーススキーマを更新。
課題4: Unknown argument 'image' (Prisma検証)
- 問題:
passwordエラー解決後、今度はimageフィールドが不明な引数としてエラーになる。 - 原因: Facebookからユーザーのプロフィール画像URL(
image)が提供されるが、Userモデルにimageフィールドが定義されていなかったため。 - 解決:
Userモデルにimage String?フィールドを追加し、npx prisma migrate devでデータベーススキーマを更新。
フェーズ2: Cloudflare Pagesデプロイ時のTypeScript型エラー
ローカルでのFacebookログインが成功したものの、Cloudflare Pagesへのデプロイ時にTypeScriptの型エラーが多発。
課題1: staffNationalityの型不一致
- 問題:
my-page/page.tsxでJobListに渡すstaffNationalityの型がstring[]とstringで不一致。 - 原因:
prisma/schema.prismaではstaffNationalityがstringなのに、@/lib/mockdataの古いJob型定義ではstring[]になっており、my-page.tsxで.split(',')を使って無理やり配列に変換していたため。 - 解決:
my-page/page.tsxでJobの型を@prisma/clientからインポートするように変更。staffNationalityを配列に変換する.split(',')の処理を削除。
課題2: currentUserプロパティの欠落
- 問題:
my-page/page.tsxがJobListにcurrentUserを渡しているが、JobListPropsにcurrentUserが定義されていない。 - 原因:
src/components/JobList.tsxのJobListPropsでcurrentUserがコメントアウトされていたため。 - 解決:
JobListPropsにcurrentUser: any;を追加し、JobListコンポーネントがcurrentUserをJobCardに渡すように修正。
課題3: currentUserの型不一致 (詳細)
- 問題:
page.tsxからJobListingClientに渡すcurrentUserの型が複雑で、JobListingClientが期待する型と合わない。特にemailがstring | null | undefinedになる可能性。 - 原因:
session?.userの型が複雑な交差型になっており、JobListingClientの厳密な型定義と合致しなかったため。 - 解決:
page.tsx内でsession?.userからid,name,email,roleを明示的に抽出し、currentUserオブジェクトをシンプルな形で再構築して渡すように修正。emailはnullを許容するように調整。
課題4: onFavoriteToggleSuccessプロパティの欠落
- 問題:
JobListingClientがJobListにonFavoriteToggleSuccessを渡しているが、JobListPropsに定義されていない。 - 原因:
JobListPropsにonFavoriteToggleSuccessが定義されていなかったため。 - 解決:
JobListPropsにonFavoriteToggleSuccess: () => void;を追加。
課題5: Event handlers cannot be passed to Client Component props
- 問題:
my-page/page.tsx(Server Component) からJobList(Client Component) にonFavoriteToggleSuccess関数を渡そうとしてエラーになる。 - 原因: Server Componentは関数をClient Componentに直接渡せないため。
- 解決:
my-page/page.tsxからJobListへのonFavoriteToggleSuccessの受け渡しを削除し、JobListPropsでonFavoriteToggleSuccessをオプショナルに変更。
課題6: change-password APIの型エラー
- 問題:
bcrypt.compareにuser.passwordを渡すと、string | null型がstring型に割り当てられないというエラー。 - 原因:
User.passwordをオプショナルにしたため、nullの可能性があるのに、APIがそのチェックをしていなかった。 - 解決:
src/app/api/user/change-password/route.tsで、bcrypt.compareの前にuser.passwordがnullでないことを確認するガード句を追加。
フェーズ3: Cloudflare Pagesデプロイ時のランタイム/設定エラー
課題1: runtime = 'edge'設定の欠落
- 問題: Cloudflare Pagesが
/api/uploadルートにexport const runtime = 'edge';がないと指摘し、ビルド失敗。 - 原因:
api/upload/route.tsにruntime設定がなかったため。 - 解決:
api/upload/route.tsにexport const runtime = 'edge';を追加。
課題2: fs/promisesとpathモジュールが見つからない
- 問題:
runtime = 'edge'設定後、api/upload/route.tsがfs/promisesやpathモジュールを見つけられないとエラーになる。 - 原因: Cloudflare Edge環境はNode.jsのファイルシステムAPIをサポートしていないため。
- 解決:
api/upload/route.tsを完全に書き換え、ローカルファイルシステムへの保存ではなく、Cloudflare R2ストレージサービスを利用するように変更。これには@aws-sdk/client-s3の導入と、R2の環境変数設定(R2_BUCKET_NAME,R2_ACCOUNT_ID,R2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY,R2_PUBLIC_URL)が必要だった。
課題3: 環境変数の設定漏れ
- 問題: デプロイ後、Facebookログインが動作しない(
next-authのエラーページにリダイレクトされる)。 - 原因: Cloudflare Pagesの環境変数に、
FACEBOOK_ID,FACEBOOK_SECRET,AUTH_SECRET,DATABASE_URL,NEXTAUTH_URLが正しく設定されていなかったため。 - 解決: ユーザーにこれらの環境変数をCloudflare Pagesに設定してもらい、
NEXTAUTH_URLは本番ドメインに設定。ローカルの.env.localも適切に設定するよう指示。
フェーズ4: デプロイ後のFacebookログインとUIの最終調整
課題1: OAuthAccountNotLinkedエラーメッセージの表示
- 問題: Facebookログイン後、既存アカウントとメールアドレスが重複する場合に、ユーザーに分かりにくいエラーが表示される。
- 解決:
login/page.tsxでURLのerrorクエリパラメータを読み取り、OAuthAccountNotLinkedの場合にユーザーフレンドリーなメッセージを表示するように実装。多言語対応も実施。
課題2: URLの#_=_
- 問題: Facebookログイン後、URLに
#_=_が付与される。 - 解決: これはFacebookの仕様であり、機能に影響のない無害なものと説明。
課題3: 「お問い合わせ」ページの追加とナビゲーションリンク
- 問題: 「お問い合わせ」ページとナビゲーションリンクの追加。
- 解決:
src/app/[locale]/contact/page.tsxを作成し、プレースホルダーコンテンツを追加。src/app/[locale]/layout.tsxのフッターに「お問い合わせ」リンクを追加。public/locales/ja/common.jsonおよび他の言語ファイルにnav_contactとcontact_関連の翻訳キーを追加(多言語対応)。contact/page.tsx内のメールアドレスを更新。
結論
このプロジェクトは、Next.jsのApp Router、NextAuth.js、Prisma、Cloudflare Pagesといったモダンな技術スタックの導入における、一般的な落とし穴と解決策の宝庫となりました。特に、Server ComponentsとClient Components間のデータフロー、環境変数管理、外部サービス(Facebook、Cloudflare R2)との連携、そして多言語対応における細かな調整が重要であることを再認識させられました。
多くの課題を乗り越え、最終的にFacebookログイン機能は完全に動作し、ファイルアップロード機能もCloudflare Pagesの環境に最適化されました。この経験は、今後の開発における貴重な知見となるでしょう。