はじめに
Cloudflare Pages Functions で Better Auth を使って認証機能を実装すると、想像以上に「認証そのもの」以外のところで詰まることがある。
最初は単純なログイン不具合に見えた。
しかし実際には、問題は一つではなかった。
- Better Auth が
node:async_hooksを読もうとして 500 になる get-sessionが200でもnullを返し、フロントが落ちる/loginの再読み込みで 404 になる
この記事ではこの失敗事例をそのまま材料にして、Cloudflare Pages + Better Auth の認証実装で何をどう切り分けるべきか、そして最終的にどう解決したかをまとめる。
なお、ローカル開発環境での認証テスト方法は「Cloudflare Pages + Better Auth のローカル認証テスト手順」にまとめている。
構成全体の設計については「React + Cloudflare Pages + Turso + Better Auth の構成」も合わせて参照してほしい。
最初に起きた症状
開発環境で /login を開き、Create Account を押すと、画面上は「アカウント作成に失敗しました」と表示された。
ブラウザの Network を確認すると、次のようなリクエストが失敗していた。
GET /api/auth/get-sessionが500POST /api/auth/sign-up/emailが500GET /api/admin/sessionも500
この時点では、フロントの入力値が悪いのか、auth API の base URL が悪いのか、DB が悪いのか、まだ分からない。
まず見るべきは Browser Console ではなく wrangler のログ
認証で詰まったとき、多くの場合ブラウザ Console だけでは足りない。
ブラウザ側では、せいぜい 500 Internal Server Error までしか見えなかった。拡張機能由来の chrome-extension://... ログも混ざっていて、ノイズも多い状態だった。
一方で、wrangler pages dev のターミナルログを見ると、次の原因がすぐに見つかった。
Error: No such module "node:async_hooks"
つまり、本当の原因は Better Auth のサーバー側ハンドラが Cloudflare Pages Functions 上で落ちていたことだった。
原因1: Better Auth が node:async_hooks を要求していた
このアプリでは最初、Workers ネイティブ構成を保ちたくて wrangler.toml に nodejs_compat を入れていなかった。
しかし、実際にインストールされていた Better Auth は内部的に @better-auth/core/async_hooks を使う経路があり、そこで node:async_hooks を読みに行っていた。
結果として、Cloudflare Pages Functions 側で Node 互換が有効になっていないため、auth handler が 500 になっていた。
対応
Workers ネイティブを守るために alias で逃がす案も検討したが、保守性を考えて nodejs_compat を採用した。
compatibility_flags = ["nodejs_compat"]
ただし、ここで1つ注意点がある。
wrangler.toml に書くだけでは、ローカルの pages dev 起動コマンドで期待どおり反映されない場面があった。そのため、開発環境では起動時に明示的にフラグを付ける形で確認した。
npm run build
npx wrangler pages dev dist --compatibility-date=2026-04-01 --compatibility-flags=nodejs_compat --port 8790
これで GET /api/auth/get-session 500 と POST /api/auth/sign-up/email 500 はまず一段階解消した。
原因2: get-session が 200 でも null を返し、フロントが落ちた
次に起きたのは認証 API 自体は 500 から抜けたのに、画面が次のエラーで落ちる問題だった。
Cannot read properties of null (reading 'data')
これはフロント側の route guard が、/api/auth/get-session の戻り値を楽観的に読みすぎていたことが原因だった。
Better Auth の get-session は、未ログイン時に 200 で data: null を返し得る。
しかしフロント側が payload.data を前提に読んでいたため、ここでクラッシュしていた。
対応
get-sessionのレスポンスを型ガードで確認するnullのときは未ログイン扱いで安全に流す- テストも追加する
認証 API が 500 でなくても、200 + null を正しく扱えないとログイン画面自体が壊れる。
原因3: /login の再読み込みで 404 になった
次に見つかったのは認証そのものではなく、Cloudflare Pages の SPA ルーティング問題だった。
症状はこうだ。
/loginへのクライアント遷移はできる- しかし
/loginを直接開く、または再読み込みすると 404 になる
wrangler 側のログには、次のように出ていた。
GET /login 404 Not Found
これは React Router の問題ではなく、Cloudflare Pages が /login を静的ファイルとして探してしまっていたのが原因だ。
対応
SPA fallback として _redirects を追加した。
/* /index.html 200
これを public/_redirects として置いて build し、dist/_redirects に反映させることで、/login の直アクセスと再読み込みでも index.html にフォールバックできるようになった。
最終的にどう確認したか
問題を順に潰したあと、開発環境では次の状態まで確認できた。
GET /api/auth/get-sessionは200POST /api/auth/sign-up/emailは成功すると200- パスワードが短いと
400 BAD_REQUESTで適切に弾かれる - ログイン後の一般ユーザーは
GET /api/admin/sessionで403 Forbidden - これは失敗ではなく、「認証は通っているが admin 権限がない」という正常な認可結果
確認できたことは、次のとおりだ。
- サインアップできる
- ログインできる
- セッション取得できる
- member と admin の認可境界が機能している
なお、admin 画面そのものを確認するには、作成したユーザーを DB 側で admin に昇格する必要がある。
update user
set role = 'admin'
where email = 'admin-test@example.com';
Cloudflare Pages + Better Auth の認証確認で見るべきもの
今回の失敗から、開発環境では次の3つを必ずセットで見るべきだと分かった。
1. Browser Network
認証機能では、最低限これを見る。
GET /api/auth/get-sessionPOST /api/auth/sign-up/emailPOST /api/auth/sign-in/emailGET /api/admin/session
見るべきステータスは次のとおりだ。
200: 正常、または未ログインでもnullpayload の可能性あり400: 入力バリデーションエラー401: 未ログイン403: 権限不足500: サーバー例外
2. wrangler pages dev のターミナルログ
Cloudflare Pages Functions の本当の例外はここに出る。
今回の node:async_hooks も、ブラウザよりこちらで原因が明確に分かった。
3. フロント側の route guard 実装
認証 API が正しくても、フロントがレスポンス形を誤解していれば画面は壊れる。特に get-session のように 200 + null を返し得る API は要注意だ。
今回の教訓
1. 認証不具合は一つとは限らない
最初の 500 を直しても、次にフロントの null 応答処理や SPA fallback の不足が見つかった。認証周りは連鎖的に問題が出やすい。
2. nodejs_compat は「理想」より「現実」で判断する
Cloudflare Pages Functions で Better Auth を使うなら、まず nodejs_compat を前提に考えた方が現実的だ。Workers ネイティブ構成を守るコストが高い場面では、保守性を優先した方がよい。
3. 403 は失敗ではない
ログイン後に GET /api/admin/session 403 が出ると不安になるが、これは一般ユーザーが admin ではないことを示す正常な認可結果だ。
認証と認可は分けて見る必要がある。
4. SPA の再読み込み問題は認証問題に見えやすい
/login 再読み込みで落ちると認証不具合に見えるが、実際には _redirects 不足のような Pages 側のルーティング問題であることもある。
まとめ
Cloudflare Pages Functions + Better Auth の認証実装で問題が起きたときは次の順で切り分けるのが有効だ。
wranglerのターミナルログで 500 の本当の原因を確認するnodejs_compatの必要性を判断するget-sessionの200 + nullをフロントが安全に扱えているかを見る- SPA ルートの直アクセスと再読み込みで 404 になっていないか確認する
- 最後に
403を含めた認可結果を正常系として整理する
認証機能の実装はコードを書いて終わりではない。
Cloudflare Pages Functions、Better Auth、DB、フロント側の route guard、SPA fallback まで含めて、初めて安定して動く。
もしこれから Cloudflare Pages + Better Auth を使うなら、「どう実装するか」だけでなく、「失敗したときに何をどう見るか」まで最初から決めておくことを勧める。