模組三|會員管理系統
流量會消失,名單不會——會員系統把一次性訪客變成可以再次觸及的資產。
讓學員有帳號,讓系統知道他們買了什麼
模組三能做到什麼
Google OAuth 登入
→ 一鍵用 Google 帳號登入,不需要設定密碼
→ 訪客轉換成有帳號的會員,成本最低的方式
Email + 密碼登入(備用)
→ 部分訪客不想用 Google
→ 支援標準 Email / 密碼 + 信箱驗證
會員儀表板
→ 我購買的課程
→ 我的學習進度
→ 帳號設定
Access Control 整合
→ 購買記錄 → 對應 role(l2 / l3)
→ /learn 頁面根據 role 顯示或隱藏內容
→ 整個 access control 系統的核心
最佳實務
用 Better Auth,不用 NextAuth。
Better Auth 是 2024 年後的新標準:
- 型別完整,TypeScript 原生
- 內建更多功能(roles / sessions / OAuth)
- 與 Payload CMS 整合更簡單
- 文件清楚,社群活躍
Google OAuth 是 OPC 的最佳登入方式。
對台灣讀者來說,幾乎所有人都有 Google 帳號。 一鍵登入 = 最低的摩擦 = 最高的轉換。
不要自建 session 管理,交給 Better Auth。
Better Auth 處理所有 session、cookie、refresh token 的邏輯。
你只需要在需要保護的地方呼叫 auth.api.getSession()。
Payload CMS 的 Users collection 和 Better Auth 分開維護。
Payload 的 Users 是 CMS 後台管理員(你自己)。 Better Auth 的 users 是前台會員(你的學員)。 兩者分開,不要混用。
前置條件
□ Google Cloud Console 帳號(免費)
→ 建立專案
→ 啟用 Google OAuth API
→ 建立 OAuth 2.0 Client ID
→ 記下 Client ID 和 Client Secret
→ 設定 Authorized redirect URIs:
http://localhost:3000/api/auth/callback/google(本機)
https://你的網域.com/api/auth/callback/google(Production)
□ 資料庫已連線(Postgres,在 Vercel 已設定)
Claude Code 指令
我的專案是 Next.js App Router + Payload CMS 3.x + Tailwind CSS + TypeScript。
模組一(Blog + Email)和模組二(課程系統)已完成。
請幫我建立模組三:會員管理系統,使用 Better Auth。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【A】Better Auth 安裝與設定
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
安裝 Better Auth:
npm install better-auth
在 .env.local 加入:
BETTER_AUTH_SECRET=(用 openssl rand -base64 32 生成)
BETTER_AUTH_URL=http://localhost:3000(Production 改為你的網域)
GOOGLE_CLIENT_ID=(從 Google Cloud Console 取得)
GOOGLE_CLIENT_SECRET=(從 Google Cloud Console 取得)
DATABASE_URL=(Postgres 連線字串,已存在)
建立 lib/auth.ts:
→ 設定 Better Auth,使用 Postgres adapter
→ 啟用 Google OAuth provider
→ 啟用 Email + Password provider
→ 設定 session 過期時間:30 天
→ 啟用 roles 功能(roles:free / l2 / l3 / admin)
→ 設定 session callbacks:在 session 中包含 user.role
建立 app/api/auth/[...all]/route.ts:
→ 使用 Better Auth 的 Next.js handler
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【B】資料庫 Schema
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Better Auth 需要以下資料表,用 Better Auth CLI 自動生成 migration:
npx @better-auth/cli generate
需要的資料表(Better Auth 自動建立):
- user(id, email, name, image, role, createdAt)
- session(id, userId, token, expiresAt)
- account(id, userId, provider, providerAccountId)
- verification(id, identifier, value, expiresAt)
額外建立 Purchase 資料表(手動建立 migration):
- id(uuid, primary key)
- userId(reference → user.id)
- courseSlug(text,購買的課程)
- orderId(text,來自金流的訂單 ID)
- amount(integer,實際付款金額,台幣)
- status(text:pending / completed / refunded)
- purchasedAt(timestamp)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【C】登入 / 註冊頁面
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
建立 /login 頁面:
→ Google 登入按鈕(優先,放最上面)
→ 分隔線:「或使用 Email 登入」
→ Email + 密碼表單
→ 「還沒有帳號?點這裡註冊」連結
→ 已登入的話 redirect 到 /dashboard
建立 /register 頁面:
→ Google 登入按鈕(同樣優先)
→ 分隔線:「或用 Email 建立帳號」
→ 姓名 + Email + 密碼 + 確認密碼
→ 送出後發送驗證信(使用 Resend,RESEND_API_KEY 已設定)
→ 已登入的話 redirect 到 /dashboard
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【D】會員儀表板
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
建立 /dashboard 頁面(需要登入才能進入):
頁面結構:
我的課程 Section:
→ 從 Purchase 資料表查詢此 userId 的所有 status=completed 購買記錄
→ 顯示課程卡片(封面圖 / 標題 / 進度)
→ CTA:繼續學習 → 連結到 /learn/[courseSlug]
帳號資訊 Section:
→ 顯示:名字 / Email / 大頭照(Google 帳號的話用 Google 大頭照)
→ 編輯名字按鈕
登出按鈕
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【E】Access Control 整合到模組二
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
更新 /learn/[courseSlug]/[lessonSlug] 頁面的 access control:
在 Server Component 中:
1. 呼叫 auth.api.getSession() 取得當前使用者
2. 如果未登入 → 顯示登入提示 + 登入按鈕
3. 如果已登入,查詢 Purchase 資料表:
- 取得此 userId 購買的所有課程 slug
- 對應到 user.role(l2 或 l3)
4. 根據 Lesson 的 access 欄位和 user.role 判斷是否有權限:
- access=public 或 isFree=true → 任何人可看
- access=l2 → 需要 role 是 l2 或 l3
- access=l3 → 需要 role 是 l3
5. 無權限:顯示「購買 [課程名稱] 以解鎖此堂課」+ Landing Page 連結
建立 middleware.ts(保護 /dashboard 路由):
→ /dashboard/* 需要登入,否則 redirect 到 /login?redirect=/dashboard
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
技術規格:
- 所有 auth 相關操作在 Server Component 或 API Route 處理,不在 Client Component
- 使用 Better Auth 的 useSession hook 在 Client Component 取得 session 狀態
- 登入狀態在 Header 元件顯示(已登入:大頭照 + 我的課程 / 未登入:登入按鈕)
- 完成後提供測試 Google OAuth 的完整步驟