🕵️

Next.js 認証エラー時の無限ループ、Windows Chrome特有の謎を解き明かす

に公開2

はじめに

Rehab for JAPANの真木です。

今回は、Auth0を使った認証フローで遭遇した、ちょっと変わった問題についてお話ししたいと思います。具体的には、認証エラーが発生した際にアプリケーションが無限ループに陥ってしまう現象。しかも、これがWindows版のChromeでのみ発生し、Mac版のChromeでは全く問題なかったという、なんとも悩ましい状況でした。

本記事では、この問題をどのように解消したか、そしてなぜこのようなOS・ブラウザ依存の挙動が起きたのか、その原因を深く掘り下げていきます。


発生した環境と問題の概要

まず、問題が発生した際の環境は以下の通りです。

  • 認証サービス: Auth0
  • フロントエンド: Next.js
  • バックエンド: Ruby on Rails

具体的なシナリオとしては、ログイン後の権限チェック処理でエラーとなった場合、特定のエラー画面へ遷移させるロジックを実装していました。ところが、この遷移処理を実行すると、トップ画面とエラー画面の間でリダイレクトの無限ループが発生してしまったのです。

前述の通り、この現象はWindows版のChromeでのみ確認され、Mac版のChromeでは正常にエラー画面へ遷移していました。このOSとブラウザの組み合わせによる違いが、今回のデバッグを複雑にしました。


解決方法

真っ先に、解決策からご紹介しましょう。エラー画面への遷移の実装を、以下のように変更することで問題は解消しました。

// 変更前
window.location.href = '/error';

// 変更後
import { useRouter } from 'next/router';

// ...コンポーネント内またはロジック内で
const router = useRouter();
router.replace('/error');

たったこれだけの変更で、無限ループがピタリと止まりました。では、なぜこの変更が解決に繋がったのでしょうか。その謎を解き明かすために、Next.jsにおけるページ遷移の基本から見ていきましょう。


Next.jsにおけるページ遷移の選択肢

Next.jsでページ間を移動させる方法はいくつかありますが、主なものとしてNext.jsが提供するrouter.pushrouter.replace、そしてブラウザネイティブのwindow.location.hrefがあります。これらは似ているようで、その内部的な挙動には重要な違いがあります。

1. router.push(url)

router.pushは、Next.jsで最も広く使われるページ遷移方法です。

  • 挙動: 指定されたURLへ遷移し、ブラウザの履歴に新しいエントリを追加します。これにより、ユーザーはブラウザの「戻る」ボタンを使って前のページに戻ることができます。
  • 特徴:
    • クライアントサイドルーティング: Next.jsの基盤であるクライアントサイドルーティングを利用します。これにより、必要な部分のみをレンダリングし、高速なページ遷移を実現します。
    • <Link>コンポーネントとの連携: <Link href="...">コンポーネントも、内部的にはrouter.pushに近い挙動をします。
    • 状態の保持: 遷移先のページで必要なコンポーネントの再マウントやデータフェッチが行われますが、アプリケーション全体のグローバルな状態(Reduxストアなど)は保持されます。

2. router.replace(url)

router.replaceもNext.jsが提供するページ遷移方法ですが、router.pushとは履歴の扱いが異なります。

  • 挙動: 指定されたURLへ遷移しますが、現在のブラウザ履歴エントリを置き換えます。これにより、新しいページに遷移しても、ブラウザの「戻る」ボタンでそのページより前のページには戻れますが、置き換えられたページには戻れません。
  • 特徴:
    • クライアントサイドルーティング: router.pushと同様に、Next.jsのクライアントサイドルーティングを利用します。
    • 履歴の置き換え: 履歴を汚したくない場合や、特定のページに一方通行で遷移させたい場合(例: ログイン後のリダイレクトや、エラーページからの脱出)に非常に有効です。
    • 状態の保持: router.pushと同様に、アプリケーションのグローバルな状態は保持されます。

3. window.location.href = url

これはNext.jsの機能ではなく、ブラウザが標準で提供するJavaScriptの機能です。

  • 挙動: 指定されたURLへブラウザを完全にリロード(再読み込み)させます。これは、あたかもユーザーがブラウザのアドレスバーにURLを入力してEnterキーを押すか、通常のHTMLの<a>タグをクリックするのと同義です。
  • 特徴:
    • フルページリロード: Next.jsのクライアントサイドルーティングとは異なり、ページ全体がサーバーから再取得・再レンダリングされます。
    • 履歴への追加: ブラウザの履歴に新しいエントリが追加されます。
    • 状態のリセット: ページが完全にリロードされるため、Next.jsアプリケーションの状態(コンポーネントの状態、React Context、Reduxストアなど)はすべてリセットされます
    • ブラウザのキャッシュ挙動に強く依存: ブラウザのキャッシュ(特にBack-Forward Cache: BFCache)の挙動に直接影響を受けます。

なぜWindows版Chromeでのみ無限ループが発生したのか?

今回の問題の核心は、同じChromeなのにWindowsとMacで挙動が異なった点にあります。この違いは、主に ブラウザのキャッシュ機構(特にBFCache) と、OSごとのブラウザの挙動の微妙な差が関係していると考えられます。

推測される原因

  1. window.location.hrefによる完全リロードとBFCacheの相互作用

    • Back-Forward Cache (BFCache) は、ユーザーが「戻る」ボタンや「進む」ボタンでページを移動した際に、ページを瞬時に表示するため、そのページのメモリ上の状態を保存しておく機能です。
    • Windows版Chromeが、Mac版Chromeよりも積極的にBFCacheを利用しようとする傾向がある可能性があります。
    • window.location.hrefでエラーページに遷移した場合、エラーが発生した元の認証ページ(またはその直前の状態)がBFCacheに保存されてしまう可能性が高まります。
    • この保存された認証ページが、何らかの理由で(例えば、エラーページでログイン状態のチェックが再度行われ、認証が失敗したと判断された時など)BFCacheから復元されると、認証エラーの状態も一緒に復元されてしまいます。その結果、再びエラーページへのリダイレクトがトリガーされ、無限ループに陥ったと考えられます。
  2. Auth0 SDKとセッション状態の不整合

    • window.location.hrefによる完全リロードは、Next.jsアプリケーションのReactコンポーネントの状態や、グローバルなJavaScriptの状態をすべてリセットします。
    • Auth0のSDKが、リロードによってクッキーやセッションの状態を正しく引き継げなかったり、予期せぬタイミングで認証チェックをトリガーしたりした可能性があります。
    • Windows版Chromeが、セッションクッキーの扱いに関して、このフルリロードの挙動と組み合わさることで、Mac版とは異なるタイミングで認証エラーを再検出しやすかったのかもしれません。

router.replaceが解決策となった理由

router.replaceを使用することで問題が解決したのは、以下の理由が考えられます。

  • BFCacheの回避: router.replaceはNext.jsのクライアントサイドルーティング機構を通じて動作するため、ブラウザの完全なページリロードを伴いません。これにより、前述のBFCacheによる意図しない状態の復元を回避できます。履歴を置き換えることで、過去の認証失敗状態がキャッシュされるのを防げたと考えられます。
  • Next.jsアプリケーションの健全な状態遷移: router.replaceは、Next.jsのコンポーネントライフサイクルや状態管理とより密接に連携します。これにより、Auth0 SDKとの連携もスムーズに行われ、セッションや認証状態の不整合が起きにくくなったと考えられます。

まとめ

今回は、一見シンプルに見えるページ遷移の方法が、OSやブラウザの微妙な挙動の違いによって予期せぬ問題を引き起こす可能性があることを学びました。

Next.jsアプリケーションにおけるページ遷移では、特別な理由がない限り、Next.jsが提供するnext/routerを使用することがベストプラクティスです。

  • router.push: 通常のページ遷移、履歴にエントリを残したい場合。
  • router.replace: ログイン後のリダイレクト、エラーからの脱出など、履歴に残したくない、または前の状態に戻ってほしくない場合。
  • window.location.href: Next.jsのコンテキストを完全に離れて、ブラウザのネイティブな挙動に頼りたい場合(例: 外部サイトへの遷移、非常に特殊なフルリロードが必要な場合など)。しかし、Next.jsアプリケーション内での使用は、今回のように思わぬ問題を引き起こす可能性があるため、慎重に検討すべきです。

AIに依頼すれば、コードを書いてくれて、障害があれば、修正もしてくれる、そんな時代になってきていますが、たまには発生した問題について、なぜそれが起きたのかを深く掘り下げてみるのも良いのではないでしょうか。今回の経験が、皆さんの開発の一助となれば幸いです。

Rehab Tech Blog

Discussion

kake2kake2

とても興味深い記事をありがとうございました!

Windows ChromeとMac Chromeで挙動が異なるという問題、
39年のシステム開発経験でも、OS・ブラウザ依存の問題は
いつも悩ましいものでした。

おっしゃる通り、基本的な動作でも利用者様が遭遇したら
大きなインパクトになりますよね。特に介護現場では、
ITに不慣れな方も多いので、このような無限ループは
本当に困惑させてしまいます。

BFCacheとの相互作用まで深掘りされた分析、勉強になりました。
こうした知見をチームで共有できる文化、素晴らしいです。

「AIに頼るだけでなく、なぜ起きたのかを深く掘り下げる」
という姿勢にも共感します。根本原因を理解することで、
同様の問題を予防できますものね。
丁寧に確実に仕上げていく姿が伝わってきます!
素晴らしい! m(_ _)m 応援しています!

Atsuko MakiAtsuko Maki

コメントいただきありがとうございます!
共感とお褒めのお言葉がとても嬉しいです。
応援を励みにこれからも真摯に取り組んでいきたいと思います。