🔁

Next.js × Supabase認証でハマった無限ループ問題

に公開

はじめに

AIにコードを書かせて開発を加速するのはもはや日常ですが、そのまま鵜呑みにすると地獄を見ることもある──そんな体験談です。
今回は Next.js × Supabase 認証実装で無限ループに陥った問題を紹介しつつ、その仕組みと解決策を整理しました。

症状:無限ループにハマる

  • ページが延々と「loading...」
  • コンソールに同じエラーが連発

まさに 無限ループ地獄

なぜ無限ループが起きたのか?

1. ReactのuseEffectの落とし穴

useEffectは依存配列の値が変化するたびに実行されます。依存配列を空にしても、副作用内で状態更新を繰り返すと再レンダリングが誘発され、結果としてループに陥ることがあります。

https://ja.react.dev/reference/react/useEffect

2. SupabaseのonAuthStateChange

Supabaseの認証状態はイベントで通知されます。

  • SIGNED_IN
  • SIGNED_OUT
  • TOKEN_REFRESHED

AIが生成したコードは、TOKEN_REFRESHED のたびに checkUserRole() を呼び出し、状態更新が再レンダリングを誘発。結果として「イベント発火 → 状態更新 → 再レンダリング → イベント発火…」の無限ループになりました...

https://supabase.com/docs/reference/javascript/auth-onauthstatechange

悪い例と良い例

悪い例(AI生成コード)

useEffect(() => {
  const subscription = supabase.auth.onAuthStateChange(async (event, session) => {
    if (event === 'TOKEN_REFRESHED') {
      await checkUserRole(); // 毎回実行
    }
  });
}, []); // 依存配列が空

このままだと「TOKEN_REFRESHEDのたびに状態更新 → 再レンダリング → イベント発火…」で無限ループ。

良い例(修正版)

const [isChecking, setIsChecking] = useState(false);

useEffect(() => {
  const subscription = supabase.auth.onAuthStateChange(async (event, session) => {
    if (event === 'TOKEN_REFRESHED' && !isChecking) {
      if (!user || !isAdmin) {
        setIsChecking(true);
        await checkUserRole();
        setIsChecking(false);
      }
    }
  });

  return () => subscription.unsubscribe();
}, [user, isAdmin, isChecking]);

修正ポイント:

  1. isChecking フラグで多重実行を防止
  2. 必要なときだけ checkUserRole を実行
  3. unsubscribe を返してクリーンアップ

https://supabase.com/docs/guides/auth/troubleshooting

まとめ

AIは便利ですが、「副作用の制御」「イベント駆動の挙動」といった文脈依存の強い領域では誤ったコードを提案することがあります。
個人的には、特に認証周りでかなり不安の残る実装が多い気がしています。

Discussion