{
  "id": "add-better-auth-to-a-nextjs-app",
  "type": "playbooks",
  "category": "playbooks",
  "locale": "pt",
  "url": "/pt/playbooks/add-better-auth-to-a-nextjs-app",
  "title": "Prompt-to-PR: Adicionar Better Auth a um App Next.js",
  "description": "SOP completo para integrar Better Auth a um projeto Next.js App Router — sessões, email/senha, OAuth e proteção de middleware em uma única execução.",
  "tools": [
    "Cursor",
    "Claude Code",
    "Codex",
    "Windsurf"
  ],
  "stack": [
    "Next.js",
    "TypeScript",
    "PostgreSQL"
  ],
  "tags": [
    "auth",
    "nextjs",
    "typescript",
    "postgres"
  ],
  "difficulty": "medium",
  "updated": "2026-06-08",
  "markdown": "Um playbook completo para adicionar [Better Auth](https://www.better-auth.com/) a um projeto Next.js 15 App Router existente com PostgreSQL. Abrange o servidor de autenticação, manipulador de rotas, helper do cliente, middleware e UI de login em uma única execução do agente.\n\n## 1. Requisito\n\nAdicionar autenticação baseada em sessão com email/senha e GitHub OAuth. Proteger todas as rotas em `/dashboard` via middleware do Next.js. Armazenar sessões no banco de dados PostgreSQL existente usando o adaptador Drizzle integrado do Better Auth.\n\n## 2. Primeiro Prompt\n\n```txt title=\"First Prompt\"\nAdd Better Auth to this Next.js 15 App Router project.\n\nStack: PostgreSQL (Drizzle ORM, schema in src/db/schema.ts), TypeScript, Tailwind.\n\nTasks:\n1. Install `better-auth` and `@better-auth/drizzle`.\n2. Create `src/lib/auth.ts` — configure BetterAuth with the Drizzle adapter,\n   emailAndPassword plugin, and GitHub socialProvider. Read secrets from\n   BETTER_AUTH_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET env vars.\n3. Create `src/app/api/auth/[...all]/route.ts` that re-exports the auth handler\n   for GET and POST.\n4. Create `src/lib/auth-client.ts` that exports a `createAuthClient()` instance\n   for client components.\n5. Add Better Auth tables to `src/db/schema.ts` using `auth.api.generateSchema()`.\n6. Create `src/middleware.ts` that protects every route under `/dashboard`;\n   redirect unauthenticated requests to `/sign-in`.\n7. Create `src/app/(auth)/sign-in/page.tsx` — a minimal email/password form and\n   a \"Continue with GitHub\" button using the auth client.\n8. Do not touch any existing route or component unless strictly necessary.\n```\n\n## 3. Alterações de Arquivos Esperadas\n\n```txt\npackage.json                              (better-auth, @better-auth/drizzle)\nsrc/lib/auth.ts                           (new — server auth instance)\nsrc/lib/auth-client.ts                    (new — browser auth client)\nsrc/app/api/auth/[...all]/route.ts        (new — catch-all route handler)\nsrc/db/schema.ts                          (new tables: user, session, account, verification)\nsrc/middleware.ts                         (new — route protection)\nsrc/app/(auth)/sign-in/page.tsx           (new — sign-in UI)\n.env.local.example                        (BETTER_AUTH_SECRET, GITHUB_* vars)\n```\n\n## 4. Lista de Verificação de Revisão\n\n- `BETTER_AUTH_SECRET` tem pelo menos 32 bytes aleatórios — nunca codificado.\n- O manipulador de rota catch-all exporta tanto `GET` quanto `POST`.\n- `src/middleware.ts` usa `matcher` para pular `_next/static`, `_next/image` e `/api/auth`.\n- O adaptador Drizzle recebe a mesma instância `db` usada em outros lugares — sem segunda conexão.\n- `createAuthClient()` em `auth-client.ts` aponta para o `baseURL` correto (lê `NEXT_PUBLIC_APP_URL`).\n- A página de login é um Client Component (`\"use client\"`) — sem imports exclusivos do servidor.\n- A URL de callback do GitHub OAuth nas configurações do app GitHub corresponde a `/api/auth/callback/github`.\n- Novas tabelas de esquema são adicionadas, não substituindo, as tabelas existentes.\n\n## 5. Comandos de Teste\n\n```bash\n# Generate and run the new auth migrations\nnpx drizzle-kit generate\nnpx drizzle-kit migrate\n\n# Start dev server\nbun dev\n\n# Smoke-test: attempt to hit protected route without a session\ncurl -I http://localhost:3000/dashboard\n# Expect: 307 redirect to /sign-in\n\n# Run any existing test suite\nbun test\n```\n\n## 6. Falhas Comuns\n\n- **`BETTER_AUTH_SECRET` ausente em tempo de execução** — Better Auth lança erro na inicialização. Adicione ao `.env.local`; confirme com `console.log(process.env.BETTER_AUTH_SECRET?.length)` em `auth.ts` (remova depois).\n- **Conexão Drizzle duplicada** — o agente cria uma segunda chamada `drizzle()` dentro de `auth.ts` em vez de importar de `src/db/index.ts`. Causa dois pools de conexão.\n- **Middleware corresponde a `/api/auth`** — causa loop infinito de redirecionamento. O `matcher` deve excluir `/api/auth/(.*)`.\n- **Client Component importa `auth` exclusivo do servidor** — erro de build. Apenas `auth-client.ts` deve ser importado em Client Components.\n- **URL de callback do GitHub incorreta** — OAuth falha silenciosamente. Confirme que o callback do app GitHub OAuth é `http://localhost:3000/api/auth/callback/github` em desenvolvimento.\n\n## 7. Prompt de Correção\n\n```txt title=\"Fix Prompt\"\nThe middleware is redirecting /api/auth/* to /sign-in, causing an infinite loop.\n\nFix src/middleware.ts so the matcher explicitly excludes:\n- /api/auth/:path*\n- /_next/static/:path*\n- /_next/image\n- /favicon.ico\n\nAlso confirm that auth.ts imports `db` from `src/db/index.ts` rather than\ncreating a second Drizzle instance.\n```\n\n## 8. Descrição do PR\n\n```md title=\"PR description\"\n## Auth: Add Better Auth (email/password + GitHub OAuth)\n\n- Installs `better-auth` with Drizzle adapter against the existing PostgreSQL db\n- Catch-all `/api/auth/[...all]` route handles all auth requests\n- Middleware protects `/dashboard/**`; unauthenticated → `/sign-in`\n- New sign-in page with email/password form and GitHub OAuth button\n- Four new schema tables (`user`, `session`, `account`, `verification`) via migration\n\n**Env vars required** (see `.env.local.example`):\n- `BETTER_AUTH_SECRET` — min 32-byte random string\n- `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`\n- `NEXT_PUBLIC_APP_URL`\n```"
}