# Prompt-to-PR：为 Next.js 应用添加 Better Auth

> 完整的 SOP，用于将 Better Auth 接入 Next.js App Router 项目——一次性完成会话、邮箱/密码登录、OAuth 和中间件保护。

**Type:** Playbook  
**Tools:** Cursor, Claude Code, Codex, Windsurf  
**Stack:** Next.js, TypeScript, PostgreSQL  
**Difficulty:** medium  
**Updated:** 2026-06-08

---

一个完整的操作手册，用于为基于 PostgreSQL 的现有 Next.js 15 App Router 项目添加 [Better Auth](https://www.better-auth.com/)。涵盖身份验证服务器、路由处理器、客户端辅助函数、中间件和登录 UI，均在单次 Agent 运行中完成。

## 1. 需求

添加基于会话的身份验证，支持邮箱/密码登录和 GitHub OAuth。通过 Next.js 中间件保护 `/dashboard` 下的所有路由。使用 Better Auth 内置的 Drizzle 适配器将会话存储在现有的 PostgreSQL 数据库中。

## 2. 初始提示词

```txt title="First Prompt"
Add Better Auth to this Next.js 15 App Router project.

Stack: PostgreSQL (Drizzle ORM, schema in src/db/schema.ts), TypeScript, Tailwind.

Tasks:
1. Install `better-auth` and `@better-auth/drizzle`.
2. Create `src/lib/auth.ts` — configure BetterAuth with the Drizzle adapter,
   emailAndPassword plugin, and GitHub socialProvider. Read secrets from
   BETTER_AUTH_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET env vars.
3. Create `src/app/api/auth/[...all]/route.ts` that re-exports the auth handler
   for GET and POST.
4. Create `src/lib/auth-client.ts` that exports a `createAuthClient()` instance
   for client components.
5. Add Better Auth tables to `src/db/schema.ts` using `auth.api.generateSchema()`.
6. Create `src/middleware.ts` that protects every route under `/dashboard`;
   redirect unauthenticated requests to `/sign-in`.
7. Create `src/app/(auth)/sign-in/page.tsx` — a minimal email/password form and
   a "Continue with GitHub" button using the auth client.
8. Do not touch any existing route or component unless strictly necessary.
```

## 3. 预期的文件变更

```txt
package.json                              (better-auth, @better-auth/drizzle)
src/lib/auth.ts                           (new — server auth instance)
src/lib/auth-client.ts                    (new — browser auth client)
src/app/api/auth/[...all]/route.ts        (new — catch-all route handler)
src/db/schema.ts                          (new tables: user, session, account, verification)
src/middleware.ts                         (new — route protection)
src/app/(auth)/sign-in/page.tsx           (new — sign-in UI)
.env.local.example                        (BETTER_AUTH_SECRET, GITHUB_* vars)
```

## 4. 审查清单

- `BETTER_AUTH_SECRET` 至少为 32 个随机字节——绝不能硬编码。
- 全匹配路由处理器同时导出 `GET` 和 `POST`。
- `src/middleware.ts` 使用 `matcher` 跳过 `_next/static`、`_next/image` 和 `/api/auth`。
- Drizzle 适配器使用与其他地方相同的 `db` 实例——不创建第二个连接。
- `auth-client.ts` 中的 `createAuthClient()` 指向正确的 `baseURL`（读取 `NEXT_PUBLIC_APP_URL`）。
- 登录页面为客户端组件（`"use client"`）——不导入仅服务端的模块。
- GitHub OAuth 回调 URL 在 GitHub 应用设置中与 `/api/auth/callback/github` 匹配。
- 新增的模式表是在现有表的基础上添加，而非替换。

## 5. 测试命令

```bash
# Generate and run the new auth migrations
npx drizzle-kit generate
npx drizzle-kit migrate

# Start dev server
bun dev

# Smoke-test: attempt to hit protected route without a session
curl -I http://localhost:3000/dashboard
# Expect: 307 redirect to /sign-in

# Run any existing test suite
bun test
```

## 6. 常见故障

- **运行时 `BETTER_AUTH_SECRET` 缺失**——Better Auth 在启动时抛出错误。将其添加到 `.env.local`；在 `auth.ts` 中使用 `console.log(process.env.BETTER_AUTH_SECRET?.length)` 确认（之后删除）。
- **重复的 Drizzle 连接**——Agent 在 `auth.ts` 内部创建了第二个 `drizzle()` 调用，而不是从 `src/db/index.ts` 导入。导致两个连接池。
- **中间件匹配 `/api/auth`**——导致无限重定向循环。`matcher` 必须排除 `/api/auth/(.*)`。
- **客户端组件导入仅服务端的 `auth`**——构建错误。只有 `auth-client.ts` 应在客户端组件中导入。
- **GitHub 回调 URL 不匹配**——OAuth 静默失败。确认 GitHub OAuth 应用回调在开发环境下为 `http://localhost:3000/api/auth/callback/github`。

## 7. 修复提示词

```txt title="Fix Prompt"
The middleware is redirecting /api/auth/* to /sign-in, causing an infinite loop.

Fix src/middleware.ts so the matcher explicitly excludes:
- /api/auth/:path*
- /_next/static/:path*
- /_next/image
- /favicon.ico

Also confirm that auth.ts imports `db` from `src/db/index.ts` rather than
creating a second Drizzle instance.
```

## 8. PR 描述

```md title="PR description"
## Auth: Add Better Auth (email/password + GitHub OAuth)

- Installs `better-auth` with Drizzle adapter against the existing PostgreSQL db
- Catch-all `/api/auth/[...all]` route handles all auth requests
- Middleware protects `/dashboard/**`; unauthenticated → `/sign-in`
- New sign-in page with email/password form and GitHub OAuth button
- Four new schema tables (`user`, `session`, `account`, `verification`) via migration

**Env vars required** (see `.env.local.example`):
- `BETTER_AUTH_SECRET` — min 32-byte random string
- `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`
- `NEXT_PUBLIC_APP_URL`
```