🍣

Next.js App Router × Supabase Google認証で、公式通りなのに詰まった話

に公開
2026/01/25

Next.js App Router と Supabase を使って
Google OAuth のログインを実装しようとしました。

公式ドキュメント通りに
callback を作り、cookie を受け取り、
getUser() も呼んでいます。

ログも出ている。
cookie も見えている。
それなのに、管理画面 /admin に入れない。

最終的には
「構成として間違っていた」
という結論に辿り着きました。

この記事では、
なぜ詰まったのか
何が地雷だったのか
どういう構成なら素直に動いたのか
を、実装ログベースで整理します。

App Router × Supabase 認証で
「公式通りなのに動かない」状態にハマった人の
時間を少しでも減らせたら嬉しいです。

本当の地雷①

callback で「cookie を積んだ Response」を返していなかった

App Router の Route Handler では、

supabase.auth.exchangeCodeForSession(code)

この時点では、cookie は 「response に書き込まれる」だけ です。

つまり、次の形で cookie を積んだ response をそのまま返さないと意味がありません。

const response = NextResponse.redirect(...)
await supabase.auth.exchangeCodeForSession(code)
return response

この response をそのまま返さないと、セッションは保存されません。

やってはいけない例

途中で以下をやるとアウトです。

NextResponse.redirect(...) を作り直す

NextResponse.next() を使う

cookie をコピーし忘れる

これをやると、

ログインできている「気がする」だけ

という状態になります。

本当の地雷②

App Router で「middleware と layout の二重ガード」

当初、こんな構成にしていました。

middleware.ts

/admin をガード

app/admin/layout.tsx

getUser() で弾く

これ、やると詰まります。

理由は単純で、

middleware は Edge Runtime

layout は Server Component

cookie の見え方・更新タイミングがズレる

その結果

callback 直後:cookie 未反映

middleware が先に走る

永遠に /auth/login に戻される

公式サンプルでも、この 二重構成は推奨されていません。

本当の地雷③

App Router に NextResponse.next() を持ち込んだ

これは完全に自分のミスです。

NextResponse.next() 

は middleware 専用 です。

Route Handler(app/auth/callback/route.ts)

で使うと、即エラーになります。

NextResponse.next() was used in a app route handler

App Router は、

「返す Response は最初から最後まで一貫して扱う」

という思想です。

最終的に動いた構成

最終的に 安定した構成 はこれでした。

1️⃣ callback は「cookie を積んだ redirect を返すだけ」

exchangeCodeForSession

redirect

余計な処理をしない

2️⃣ /admin の認可は layout で一元管理

const {
  data: { user },
} = await supabase.auth.getUser()

if (!user) redirect("/auth/login")

3️⃣ middleware は使わない

App Router × 認証では、

middleware はほぼ不要

なぜ「公式通り」でも詰まったのか

理由はこれです。

Supabase の公式は Pages Router 前提

App Router のライフサイクル差分が暗黙

cookie の「いつ・どこで確定するか」が違う

つまり、

コードは正しくても、構成が間違っていた

これが今回の本質でした。


同じところで詰まっている人へ

もし今、

cookie は見える

でも getUser() が null

redirect ループする

この状態なら、

「どこで cookie を確定させているか」

を一度疑ってみてください。

おわりに

こういう

「公式通りなのにハマる話」

は、また書いていきます。

Discussion