• #eurekapu
  • #nuxt4
  • #cloudflare-pages
  • #ssr
  • #d1
  • #better-auth
  • #google-oauth
  • #認証
  • #日記
eurekapuメモ

Eurekapu Cloudflare Pages SSR化とBetter Auth認証基盤の構築

昨日の簿記問題集移行でSSGビルドが通ったところまでは確認していたが、認証機能を入れるにはサーバーサイドが必要になる。朝イチでpnpm generatepnpm buildに書き換え、Nitroプリセットをcloudflare_pagesに切り替えた瞬間から、インフラとアプリの両面を行き来する一日が始まった。

SSGからSSRへの切り替え

デプロイスクリプトの作成

別プロジェクト(tax-lp)で使っていたCloudflare Pagesデプロイ用のPowerShellスクリプトを参考に、eurekapu-nuxt4向けのスクリプトを作成した。ローカルでビルドしてwrangler pages deployで直接デプロイする方式。GitHub Actionsに依存しないので、手元で即座に確認できる。

Nitroプリセットの変更

nuxt.config.tsnitro.presetcloudflare_pagesに指定し、ビルドコマンドをpnpm generate(SSG)からpnpm build(SSR)に切り替えた。出力先が.output/public/から.output/全体に変わり、サーバー関数が含まれるようになる。

// nuxt.config.ts(変更箇所のみ)
nitro: {
  preset: 'cloudflare_pages'
}

デプロイ時のUTF-8問題

PowerShellからwranglerを呼び出す際、コミットメッセージに日本語を含めるとUTF-8エンコーディングの問題で文字化けした。コミットメッセージを英語に固定することで回避。ここは深追いせず先に進んだ。

D1データベースのセットアップ

データベース作成

wrangler d1 createでD1データベースを作成した。リージョンはAPACを指定。wrangler.tomlにbinding名とdatabase_idを追加。

Cloudflare APIでバインディング設定

Cloudflare Pagesプロジェクトに対してD1バインディングを紐づけるのは、ダッシュボードからでもできるが、今回はCloudflare API経由でproductionとpreview両方のバインディングを一括設定した。curlでPATCHリクエストを投げる形式。

SSR + API動作確認

デプロイ後に/api/healthエンドポイントを叩いてSSRとAPIルートが正常に動いていることを確認した。SSGのときは存在しなかったサーバーサイドのルートが、Cloudflare Pages上で動くのを見て「SSR化できた」と確認が取れた。

Better Auth + Google OAuth認証スケルトン

パッケージと構成

better-authパッケージをインストールし、以下のファイル群を作成した。

  • server/utils/auth.ts -- 認証コアの設定(D1データベース接続、Google OAuthプロバイダ)
  • server/plugins/ -- Nitroプラグインで認証ミドルウェアを登録
  • shared/auth-schema.ts -- 認証関連のスキーマ定義(クライアント・サーバー共有)
  • composables/useAuth.ts -- クライアント側の認証composable
  • auth.client.ts -- Nuxtクライアントプラグインで認証状態を初期化

ログイン/ログアウトUI

default.vueレイアウトのヘッダーにログイン/ログアウトボタンを配置。login.vueページにはGoogleログインボタンを設置した。認証状態に応じてUIが切り替わるだけのシンプルな実装で、まずは「認証が通る」ことだけを確認する骨組み。

CSPヘッダーの更新

セキュリティヘッダーのContent-Security-Policyに、Google OAuthで使うドメイン(accounts.google.com等)を許可リストに追加した。

SQLマイグレーション

Better Authが必要とするテーブル(user、session、account等)のSQLマイグレーションファイルを作成。D1に対してwrangler d1 executeで適用する形式。

認証テスト

Nuxt環境外でのテスト対応

Better Authの認証ロジックをVitestでテストする際、Nuxt固有のauto-importやランタイム設定に依存する部分が障壁になった。vi.mockvi.hoistedを組み合わせてNuxtのランタイムをモック化し、テストをNuxt環境の外で実行できるようにした。

// vi.hoistedでモジュールをテストの最上位でモック定義
const mocks = vi.hoisted(() => ({
  useRuntimeConfig: vi.fn(() => ({
    betterAuth: { secret: 'test-secret' },
    google: { clientId: 'id', clientSecret: 'secret' }
  }))
}))
vi.mock('#imports', () => mocks)

7テスト全パス

認証コアの初期化、セッション管理、OAuthコールバック処理など7件のテストを書いて全パス。ビルドも成功し、デプロイ可能な状態になった。

今日の学び

  • SSGからSSRへの切り替えはpresetとビルドコマンドの変更だけで済むが、出力構造が変わるのでデプロイスクリプトも連動して直す必要がある
  • D1バインディングのAPI設定は、productionとpreviewで別々にPATCHリクエストを投げる。ダッシュボードで手作業するより、スクリプト化しておくと再現性がある
  • vi.hoistedはテストファイルの巻き上げ順序を制御できるので、Nuxtのauto-import依存をモックするのに使える。これを知らなかったら#importsのモックでハマっていた
  • Better Authはセットアップのファイル数が多い(server/utils, plugins, shared, composables, client plugin)が、一つ一つは薄い。全体像を先に把握してから手を動かすと迷わなかった

次のステップ

  • Google OAuth認証情報の本番設定とエンドツーエンドでのログインテスト
  • 認証済みユーザーのみアクセスできるページのミドルウェア実装
  • 学習進捗データのD1保存(ユーザーごとの正答履歴)
  • Progressive Engagement方式のユーザー登録導線との統合