• #eurekapu
  • #better-auth
  • #google-oauth
  • #d1
  • #cloudflare-workers
  • #問題集
  • #学習履歴
  • #アクセス制御
  • #codex
  • #日記
開発eurekapuメモ

Better Auth認証基盤の本番投入と問題集機能6フェーズ拡張

前日にBetter Auth + Google OAuthの認証スケルトンと管理ダッシュボードを作り、ビルドが通るところまでは確認していた。今日は認証を本番環境に載せるところから始まり、問題集の機能拡張を6フェーズで一気に積み上げた。朝から夜までコードを書き続けて、気づいたら21ファイル・1029行が増えていた。

認証基盤の本番投入

.env設定とdevサーバーのポート固定

Better Authが参照する環境変数(BETTER_AUTH_SECRET、Google OAuthのクライアントID/シークレット)を.envに整理した。開発時のコールバックURLを安定させるため、devサーバーのポートを3200に固定。他プロジェクトの3000番とぶつかる問題がこれで消えた。

D1マイグレーション

Better Authが必要とする4テーブル(user、session、account、verification)のマイグレーションSQLを作成し、wrangler d1 executeで本番D1に適用した。前日のダッシュボード実装で踏んだ「tokenカラム不足」問題の教訓を活かし、適用後にPRAGMA table_infoで全テーブルの定義を目視確認した。

GCP ConsoleでのOAuth設定

Google Cloud ConsoleでOAuthクライアントの承認済みリダイレクトURIに本番ドメインを追加した。承認済みJavaScriptオリジンも忘れずに設定。ここを漏らすとリダイレクト先が不一致でOAuthフローが途中で止まるので、URIの一覧をスクショに残しておいた。

メール/パスワード認証は見送り

当初はメール/パスワード認証も入れる予定だったが、見送った。メール配信(パスワードリセット等)にはSESやResendの契約が必要で、月額コストが発生する。個人開発で固定費を増やしたくない。Googleログイン一本に絞れば、認証インフラの維持コストはゼロになる。迷ったが、ユーザー数が少ないうちは選択肢を減らす方が正しいと判断した。

Stripe審査待ちで決済は保留

買い切り決済にStripe Checkoutを使う予定だったが、アカウント審査が完了していない。認証基盤が先に動く状態になったので、決済部分は審査通過後に実装する。今日のスコープからは外した。

Progressive Engagementの設計

Duolingo型のProgressive Engagement方式でユーザー登録導線を設計した。いきなり「ログインしてください」と壁を出すのではなく、使い込むほど登録のメリットが見えてくる段階的なアプローチ。

アクセス3段階:

  1. 未ログイン(Free) -- 問題を解ける。学習履歴はローカルストレージに保存
  2. ログイン済み -- 学習履歴がD1に永続化される。復習機能が使える
  3. 購入済み -- 全コンテンツにアクセス可能

この設計が問題集機能の拡張方針を決定づけた。

問題集機能6フェーズ拡張

認証基盤が動いたところで、問題集の機能拡張に取り掛かった。6フェーズに分けて実装を進めた。

Phase A: 基盤整備

各問題にユニークなQuestion IDを生成し、JSONデータに埋め込んだ。D1側にも学習履歴テーブルのマイグレーションを追加。問題IDがないと履歴をどの問題に紐づけるかが定まらないので、ここが全ての土台になる。

Phase B: 履歴インフラ

useQuizHistory composableを作成。ログイン状態に応じて保存先を切り替える。

  • 未ログイン -- localStorageに保存
  • ログイン済み -- サーバーAPIを経由してD1に保存

サーバー側には回答記録のPOSTと履歴取得のGETを実装。composableが保存先の違いを吸収するので、UIコンポーネントは保存先を意識しない。

Phase C: 履歴表示

QuizHistoryDotsコンポーネントを実装した。各問題の正誤を小さなドット(緑=正解、赤=不正解、灰=未回答)で横一列に並べる。一目でどこを間違えたかが視覚的にわかる。

復習ページreview.vueも新規作成。間違えた問題だけをフィルタリングして再挑戦できる。

Phase D: 回答記録の統合

practice.vue(問題演習画面)に回答記録ロジックを統合した。問題を解くたびにuseQuizHistoryを通じて記録が走る。

未ログインユーザーに対しては、一定問題数を解いた後に「ログインすると学習履歴が保存されます」というモーダルを表示する。強制ではなく、閉じればそのまま続けられる。

Phase E: アクセスレベル制御

accessConfigで問題集ごとのアクセスレベル(Free / Login / Purchased)を定義し、useQuizAccess composableで現在のユーザー状態と照合する。

アクセス権がないコンテンツにはロックアイコンを表示。問題一覧ページで「この先はログインが必要」「この先は購入が必要」が視覚的にわかるようにした。

Phase F: ランダム10問出題

random.vueを新規作成。全問題プールからランダムに10問を選んで出題する。「今日のチャレンジ」的な使い方を想定している。シャッフルアルゴリズムはFisher-Yatesを使い、偏りが出ないようにした。

復習ページの改善

「要復習」フィルターの3段階分割

最初は「要復習」を1つのフィルターにしていたが、不正解の回数によって緊急度が違う。不正解の回数で3段階に分割した。

  • 1回間違い -- 軽い復習
  • 2回間違い -- 重点復習
  • 3回以上 -- 要注意

各フィルターに該当する問題の件数も表示し、「あと何問残っているか」が数字で見えるようにした。

3階層表示

復習ページの構造を「章 → セクション → 問題」の3階層に整理した。フラットに問題が並んでいると、全体のどの部分を復習しているのかが掴めない。章見出しとセクション見出しを挟むことで、教科書的な構造が復習ページにも反映されるようになった。

Codex(GPT-5.3)コードレビュー

6フェーズの実装が完了した時点で、Codex CLIにコードレビューを依頼した。指摘はP1(即修正)、P2(近日中に修正)、P3(改善推奨)に分類されて返ってきた。

P1の修正を即座に対応し、P2とP3は次回以降のタスクとして記録した。Codexはファイル横断のレビューが得意で、composable間の型不整合やAPIエンドポイントのエラーハンドリング漏れを拾ってくれた。

本番デプロイとnodejs_compat問題

Cloudflare Workers nodejs_compat

本番デプロイ時、Cloudflare Workersでnodejs_compatフラグが有効になっていないことが原因でBetter Authの依存パッケージがクラッシュした。Node.jsのビルトインモジュール(crypto等)をWorkersランタイムで使うには、wrangler.tomlcompatibility_flags = ["nodejs_compat"]を明示的に追加する必要がある。

# wrangler.toml
compatibility_flags = ["nodejs_compat"]

この1行がないとデプロイは成功するがランタイムでエラーになる。ビルド時にはエラーが出ないので、実際にリクエストを飛ばして初めて気づく類の問題だった。

ローカルストレージからD1への移行フロー

未ログイン時にローカルストレージに溜まった学習履歴を、ログイン後にD1へ移行するフローを実装した。ログイン成功時にlocalStorageから履歴を読み出し、サーバーAPIにバルクPOSTで送信する。送信成功後にローカルの履歴をクリアする。

このフローがないと、ログイン前に解いた問題の履歴が消えてしまい、ユーザー体験を損なう。Progressive Engagementの設計では「ログイン前の行動を無駄にしない」のが肝になる。

今日の学び

  • Googleログイン一本に絞ると、メール配信インフラが不要になり、固定費と実装範囲の両方が縮む。ユーザー数が読めない段階では選択肢を減らすことが正解だった
  • nodejs_compatフラグはビルド時にチェックされない。デプロイ後にランタイムで落ちるので、Node.jsのビルトインモジュールを使うパッケージを導入したら、まずwrangler.tomlを確認する手順を体に染み込ませる
  • ローカルストレージ→D1の移行フローは、ログイン後の初回リクエストに組み込むのが自然。ユーザーに「データ移行中」を意識させない設計にした
  • 復習フィルターの粒度は、使ってみて初めて「もっと細かく分けたい」と感じる。最初からMAX細かくするのではなく、使いながら刻んでいく方が的確な分割になる
  • Codexのコードレビューはファイル横断の整合性チェックに向いている。手で見ると見落としがちなcomposable間の型不整合を拾ってくれた

次のステップ

  • Stripe審査通過後に買い切り決済を実装する
  • 学習進捗のダッシュボード表示(管理画面側)
  • Codex P2/P3指摘への対応
  • 残りの章の問題データ移行と動作確認