Build a Complete Auth Flow with Better Auth + shadcn + Resend
Every SaaS hits the same wall in week one: auth. This workflow takes you from `npx create-next-app` to a working email/password + magic link + email verification + protected dashboard in ~40 minutes. Built on Better Auth v1.6.11 (Auth.js merged into it as of 2026), shadcn CLI v4 (March 2026 release with skills + presets), and Resend with React Email. Covers the Next.js 16 `proxy.ts` rename, the CVE-2025-29927 defense-in-depth pattern, and the migration step where 90% of Reddit threads die.
Skills
Better Auth Best Practices
Better Auth patterns for AI agents — OAuth, session management, protected routes, plugins. Replaces Auth.js v4 patterns your model may still default to.
npx skills add better-auth/skills/better-authCreate Auth
Full Better Auth setup with social providers, 2FA, and email verification scaffolded in one shot.
npx skills add better-auth/skills/create-authshadcn/ui
Official shadcn skill — teaches the CLI v4, registry system, and Tailwind v4 config.
npx skills add shadcn/uiResend Agent Skills
Official Resend skills — React Email templates, environment setup, webhook patterns.
npx skills add resend/resend-skillsFrontend Design
Anthropic's anti-slop UI skill. Stops your agent from defaulting to generic gradient sign-in screens.
npx skills add anthropics/skills/frontend-designNext.js Best Practices
App Router conventions, Server vs Client Components, and the proxy.ts pattern for Next.js 16.
npx skills add vercel-labs/next-skills/next-best-practicesMCP Servers
Setup
Start with the shadcn CLI v4 since it now ships first-class Next.js scaffolding. `--base radix` picks Radix as the primitive layer; the CLI handles Tailwind v4 config, the `cn` util, and `components.json` automatically.
npx shadcn@latest init --template next --base radix --name my-saas
cd my-saasAdding skills BEFORE you write code is the move most people skip. Without them, your agent falls back to whatever Auth.js v4 patterns it was trained on. Restart your agent session after install.
npx skills add better-auth/skills/better-auth
npx skills add better-auth/skills/create-auth
npx skills add shadcn/ui
npx skills add resend/resend-skills
npx skills add anthropics/skills/frontend-design
npx skills add vercel-labs/next-skills/next-best-practicesThe `auth init` command (new in v1.6) walks you through database adapter selection, base URL, and secret generation. Pick the Drizzle adapter if you're on Neon or Supabase.
npm install better-auth
npm install @better-auth/cli --save-dev
npx auth initCreate `src/lib/auth.ts`. `requireEmailVerification: true` and `autoSignIn: false` force email verification before first sign-in. The plugins array adds 2FA via OTP and passwordless magic links.
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
import { resend } from "@/lib/resend";
import { VerifyEmail } from "@/emails/verify-email";
export const auth = betterAuth({
appName: "my-saas",
database: drizzleAdapter(db, { provider: "pg" }),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
minPasswordLength: 12,
autoSignIn: false,
},
emailVerification: {
sendOnSignUp: true,
autoSignInAfterVerification: true,
sendVerificationEmail: async ({ user, url }) => {
await resend.emails.send({
from: "auth@yourdomain.com",
to: user.email,
subject: "Verify your email",
react: VerifyEmail({ url, name: user.name }),
});
},
},
socialProviders: {
github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! },
google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! },
},
plugins: [twoFactor({ method: "otp" }), magicLink({ /* sendMagicLink ... */ })],
});This is where 90% of 'it doesn't work' Reddit threads die. The verification token table won't exist until you run the migration.
npx auth generate --adapter drizzle
npx auth migrateVerify your domain in the Resend dashboard before production. In dev, use `onboarding@resend.dev` as your `from` address. React Email templates render to HTML + plain text, work in dark mode in Apple Mail and Outlook.
npm install resend @react-email/components
# Create src/emails/verify-email.tsx as a React component
# using Button, Container, Heading, Html, Text from @react-email/componentsAdd the shadcn components you need. The form pattern uses Better Auth's typed `authClient.signIn.email` and `authClient.signIn.magicLink` — no manual fetch calls, full TypeScript inference.
npx shadcn@latest add button input label card form sonner
# Then create src/app/(auth)/sign-in/page.tsx using authClient.signIn.email**The Next.js 16 gotcha.** `middleware.ts` is now `proxy.ts` at the project root. Only check cookie presence here — full session validation happens at the DAL layer (defense in depth after CVE-2025-29927).
// src/proxy.ts
import { NextResponse, type NextRequest } from "next/server";
import { getSessionCookie } from "better-auth/cookies";
export function proxy(request: NextRequest) {
const sessionCookie = getSessionCookie(request);
if (!sessionCookie && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/sign-in", request.url));
}
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*", "/settings/:path*"] };Per the Next.js team's recommendation after CVE-2025-29927: proxy/middleware does a fast cookie check; the DAL re-checks inside Server Components before sensitive data loads. Even if an attacker spoofs middleware headers, they hit a closed door at the data layer.
// src/lib/dal.ts
import { cache } from "react";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";
export const getSession = cache(async () => {
return await auth.api.getSession({ headers: await headers() });
});
export async function requireAuth() {
const session = await getSession();
if (!session) redirect("/sign-in");
return session;
}Better Auth routes everything — sign-in, sign-up, verification, password reset, OAuth callbacks, magic links — through one catch-all handler. Deploy with `vercel --prod`; remember to set BETTER_AUTH_URL, BETTER_AUTH_SECRET, RESEND_API_KEY, and OAuth client IDs in your env.
// src/app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);Use Cases
- B2C SaaS with email/password + social logins
- Indie hacker MVPs needing magic-link sign-in
- Apps requiring email verification before first sign-in
- Products adding 2FA via OTP
- Next.js 16 apps migrating from Auth.js v4