Next.js(React)で Firebase のメール認証を実装していたとき、開発環境でのみ
「認証コードが無効です(auth/invalid-action-code)」 が発生しました。
- 本番環境 → 問題なし
- 開発環境(
next dev
) → 失敗
しかも oobCode(認証コード)は正しい はずなのに、なぜか失敗する。
目次
発生した問題|メール認証が開発環境でのみ失敗する
※メール認証リンクが届くまでのフローは割愛
- ユーザーがメール認証リンクをクリック
oobCode
(認証コード)付きの URL でアプリに戻るapplyActionCode()
を実行 → 一瞬成功- 直後に
FirebaseError: Firebase: Error (auth/invalid-action-code)
が発生して失敗扱いになる。oobCodeは正しいっぽい。
実際のコード
useEffect(() => {
const queryParams = new URLSearchParams(window.location.search)
const oobCode = queryParams.get('oobCode');
if (!oobCode) {
setError("認証コードが見つかりません。");
return;
}
const auth = getAuth();
applyActionCode(auth, oobCode)
.then(() => {
setIsSuccess(true);
setError(null);
})
.catch((e) => {
setError(`認証に失敗した可能性があります。<br />フォームよりお問い合わせください。`);
console.error("Error applying action code:", e);
setIsSuccess(false);
})
}, []);
原因|Strict Modeによるコンポーネントの二重レンダリング
Next.js の開発環境では reactStrictMode
がデフォルトで有効になっており、同じコンポーネントを意図的に2回レンダリング します。
(参考)StrictModeとは:https://ja.react.dev/reference/react/StrictMode
メール認証のフローでは、これが次のような問題を引き起こしていたもよう🤔
1回目のレンダリング:
useEffect
が走りapplyActionCode()
実行- コードが正常に処理され「使用済み」状態になる
2回目のレンダリング:
- 再び
useEffect
が走りapplyActionCode()
実行 - 既に使われたコードなので
auth/invalid-action-code
が返る
結果としてユーザーには「失敗」画面が出てしまいます。
(本番環境は reactStrictMode
無効なのでこの挙動は発生しない)
調査|reactStrictModeを一時的に無効にする
本当にreactStrictModeが原因なのか?確認するなら、reactStrictModeを一時的に無効にしてみるのが手っ取り早かった。
next.config.js
ファイルを編集し、reactStrictMode
を一時的に無効にすることで、開発環境での二重レンダリングを停止させます。
// next.config.js
サーバーを再起動すれば、開発環境でもメール認証が通るように🎉 = StrictModeが原因!
対応|useRefでuseEffectの初回実行を制御する
reactStrictMode
は潜在的なバグを検出するための重要な機能なので、恒久的に OFF にするのは非推奨。ということで、対応としてuseRef
などで useEffect
の初回実行を制御する実装に修正します。
const isInitialMount = useRef(true);
useEffect(() => {
// 開発環境のStrictModeによる二重実行を回避
if (!isInitialMount.current) return;
isInitialMount.current = false;
}, []);
まとめ|Next.js × Firebaseで開発環境だけメール認証がauth/invalid-action-codeエラーになる原因はStrict Modeだった
開発環境のみレンダリングが2度走っていることは知っていましたが、それが今回のメール認証エラーの原因だと気がつくまでちょっと時間がかかりました・・・!
Strict Modeをオフにするだけで手っ取り早く解決したいところだったのですが、根本の解決には至っていないということで、useRefを使ってuseEffectの初回実行を制御することとしました。