Better Auth vs Clerk vs NextAuth: 2026 SaaS Showdown
Better Auth vs Clerk vs NextAuth: 2026 SaaS Authentication Showdown
Authentication was a solved problem — until it wasn't. NextAuth (now Auth.js) dominated the Next.js auth landscape for years. Then Clerk raised the UX bar with polished pre-built components. Now Better Auth has emerged as a third option: self-hosted like NextAuth, feature-complete like Clerk, and MIT-licensed. This showdown compares all three with actual code examples, current 2026 pricing, and specific recommendations for SaaS builders.
TL;DR
Clerk is the fastest path to production auth with the best UI components and Next.js integration — worth the cost if you have budget and don't want to think about auth. Better Auth is the best free option in 2026 — more complete than NextAuth, no vendor lock-in, built-in passkeys/2FA/organizations. NextAuth v5 (Auth.js) is still the right choice if you need the largest ecosystem and don't mind missing built-in 2FA and RBAC. All three work on Vercel's Edge runtime in 2026.
Key Takeaways
- Better Auth: MIT, self-hosted, built-in 2FA + passkeys + organizations + RBAC — no database vendor required
- Clerk: $0.02/MAU after 10,000 free MAUs — best pre-built components, fastest setup (~5 minutes)
- NextAuth v5: MIT, self-hosted, largest ecosystem — but still missing built-in 2FA and passkeys
- Break-even: Clerk costs ~$100/month at 5,000 MAUs — Better Auth makes sense above that
- Organizations: Better Auth and Clerk both have built-in org/team management; NextAuth requires manual implementation
- Boilerplate adoption: T3 defaults to NextAuth; Supastarter supports Clerk; MakerKit uses Supabase Auth; Better Auth gaining fast in 2025–2026
The State of Next.js Auth in 2026
The auth landscape shifted significantly in 2025. Key events:
- Auth.js v5 released: NextAuth rebranded to Auth.js, added Edge runtime support, improved server action integration
- Better Auth v1.0: First stable release brought plugin architecture for OAuth, 2FA, passkeys, and organizations
- Clerk Organizations GA: Clerk's multi-tenancy features reached general availability, making it a viable Supastarter alternative
- Passkeys adoption: WebAuthn passkeys became mainstream — all three libraries now support them
The practical impact: for new SaaS projects in 2026, the choice is no longer "NextAuth or Clerk." Better Auth is a genuine contender.
Better Auth: Setup and Code
Better Auth is framework-agnostic and integrates with any database via Drizzle or Prisma adapters.
Installation
npm install better-auth
Server Configuration
// lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { twoFactor } from "better-auth/plugins";
import { passkey } from "better-auth/plugins";
import { organization } from "better-auth/plugins";
import { db } from "./db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
plugins: [
twoFactor(),
passkey(),
organization({
allowUserToCreateOrganization: true,
organizationLimit: 5,
creatorRole: "owner",
membershipLimit: 100,
}),
],
});
API Route Handler
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
Client Usage
// Client component
"use client";
import { authClient } from "@/lib/auth-client";
export function SignInButton() {
const { data: session } = authClient.useSession();
const signIn = async () => {
await authClient.signIn.social({ provider: "google" });
};
if (session) {
return <button onClick={() => authClient.signOut()}>Sign out</button>;
}
return <button onClick={signIn}>Sign in with Google</button>;
}
Organization Management
// Create and switch organizations
const { data: org } = await authClient.organization.create({
name: "Acme Corp",
slug: "acme",
});
// Invite member with role
await authClient.organization.inviteMember({
email: "user@acme.com",
role: "member",
organizationId: org.id,
});
Schema generation: Better Auth generates database migrations automatically:
npx better-auth generate
# ✓ Generated 8 tables: user, session, account, verification,
# twoFactor, passkey, organization, member
Clerk: Setup and Code
Clerk is a managed service — no database schemas to manage, no auth logic to maintain.
Installation
npm install @clerk/nextjs
Middleware (Required)
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isPublicRoute = createRouteMatcher(["/", "/sign-in(.*)", "/sign-up(.*)"]);
export default clerkMiddleware((auth, request) => {
if (!isPublicRoute(request)) {
auth().protect();
}
});
export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};
Layout Integration
// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}
Pre-built UI Components
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs";
export default function SignInPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<SignIn />
</div>
);
}
That's it. Clerk's <SignIn /> component includes email/password, social OAuth, magic links, passkeys, MFA, and CAPTCHA. No custom UI needed.
Server-Side Auth
// app/dashboard/page.tsx
import { auth, currentUser } from "@clerk/nextjs/server";
export default async function Dashboard() {
const { userId } = auth();
const user = await currentUser();
if (!userId) redirect("/sign-in");
return <div>Welcome, {user?.firstName}</div>;
}
Organizations
// Create org in the Clerk dashboard or via API
import { useOrganization, useOrganizationList } from "@clerk/nextjs";
function OrgSwitcher() {
const { organization } = useOrganization();
const { userMemberships } = useOrganizationList({
userMemberships: { infinite: true },
});
return (
<select>
{userMemberships.data?.map((mem) => (
<option key={mem.organization.id} value={mem.organization.id}>
{mem.organization.name}
</option>
))}
</select>
);
}
NextAuth v5 (Auth.js): Setup and Code
Installation
npm install next-auth@beta
Configuration
// auth.ts
import NextAuth from "next-auth";
import Google from "next-auth/providers/google";
import GitHub from "next-auth/providers/github";
import Credentials from "next-auth/providers/credentials";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { db } from "@/db";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [
Google,
GitHub,
Credentials({
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
authorize: async (credentials) => {
// Your credential validation logic
const user = await validateCredentials(credentials);
return user ?? null;
},
}),
],
callbacks: {
session({ session, token }) {
session.user.id = token.sub!;
return session;
},
},
});
Route Handler
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
Protected Route
// app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function Dashboard() {
const session = await auth();
if (!session?.user) redirect("/api/auth/signin");
return <div>Welcome, {session.user.name}</div>;
}
What's missing in NextAuth v5: There's no built-in 2FA, no passkeys, no organizations, no RBAC. You implement all of these manually.
Feature Comparison: 2026
| Feature | Better Auth | Clerk | NextAuth v5 |
|---|---|---|---|
| License | MIT | Proprietary (SaaS) | MIT |
| Hosting | Self-hosted | Managed | Self-hosted |
| Email/password | ✓ | ✓ | ✓ |
| OAuth providers | ✓ (40+) | ✓ (20+) | ✓ (50+) |
| Magic links | ✓ | ✓ | ✓ |
| Passkeys | ✓ (plugin) | ✓ | ✗ |
| 2FA / TOTP | ✓ (plugin) | ✓ | ✗ |
| Organizations | ✓ (plugin) | ✓ | ✗ |
| RBAC | ✓ (plugin) | ✓ | ✗ |
| User impersonation | ✓ | ✓ (Enterprise) | ✗ |
| Session management | ✓ | ✓ | ✓ |
| Edge runtime | ✓ | ✓ | ✓ (v5) |
| Pre-built UI | ✗ | ✓ | ✗ |
| Admin dashboard | ✗ | ✓ | ✗ |
| TypeScript | Excellent | Excellent | Good |
| Database required | Yes (own) | No | Yes (own) |
| Setup time | ~30 min | ~5 min | ~45 min |
| MAU pricing | Free (infra cost) | $0.02/MAU >10K | Free (infra cost) |
Pricing Deep Dive
Clerk 2026 Pricing
| Tier | Monthly | MAU Included | Per MAU Over |
|---|---|---|---|
| Free | $0 | 10,000 | N/A (blocked) |
| Pro | $25 | 10,000 | $0.02/MAU |
| Enterprise | Custom | Custom | Custom |
Clerk's Organizations feature requires the Pro plan. At scale:
- 5,000 MAUs → $0 (free tier)
- 15,000 MAUs → $25 + (5,000 × $0.02) = $125/month
- 50,000 MAUs → $25 + (40,000 × $0.02) = $825/month
For SaaS with 50K active users, $825/month is reasonable. For high-growth consumer apps hitting 500K MAUs, the math changes fast.
Better Auth Costs
Better Auth itself is free. Your costs:
- Database: Your existing Postgres (Supabase, Neon, PlanetScale) — already paid
- Email: Resend ($20/month for 50K emails), Postmark, or SendGrid for verification emails
- Infrastructure: No additional compute required beyond your Next.js app
Total additional cost: ~$20–50/month regardless of user count.
NextAuth v5 Costs
Same as Better Auth: free library, pay for your own database and email provider. The difference is that auth complexity (2FA, passkeys) requires building or third-party libraries on top.
Edge Runtime Support in 2026
All three auth libraries now support Vercel Edge runtime and Cloudflare Workers.
Better Auth on Edge:
// app/api/auth/[...all]/route.ts
export const runtime = "edge";
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth.handler);
Clerk on Edge: Works by default — Clerk's middleware already runs on Edge.
NextAuth v5 on Edge:
// auth.ts — use JWT strategy for Edge compatibility
export const { handlers, auth } = NextAuth({
session: { strategy: "jwt" }, // Required for Edge
// ...providers
});
Which Boilerplates Use Each
| Boilerplate | Auth Choice | Rationale |
|---|---|---|
| T3 Stack | NextAuth v5 | Ecosystem-first, community expectation |
| ShipFast | NextAuth v5 | Simplicity, no extra cost |
| Supastarter | Clerk or Supabase Auth | Choice — B2B users prefer Clerk UI |
| MakerKit | Supabase Auth | Supabase-native architecture |
| Open SaaS | Wasp built-in auth | Framework-native |
| Create T3 Turbo | NextAuth v5 | Monorepo integration |
| Indie Kit | Better Auth | Modern, fee-free |
Better Auth is gaining adoption in 2025–2026 boilerplates as it reaches stability. Expect T3-compatible Better Auth starters to grow throughout 2026.
Decision Guide
Choose Clerk if:
- Speed to auth matters more than cost
- Your team doesn't want to maintain auth infrastructure
- Pre-built UI components save significant design time
- <10,000 MAUs (free tier covers most early-stage SaaS)
- B2B SaaS where organization management UI is needed fast
Choose Better Auth if:
- Self-hosted auth is a requirement (compliance, data residency)
- You need passkeys + 2FA + organizations for free
-
10,000 MAUs where Clerk's per-MAU pricing adds up
- Your team prefers owning the full auth stack
- Framework-agnostic auth that could move to Remix or SvelteKit
Choose NextAuth v5 if:
- You're extending an existing NextAuth v4 project (v5 migration is manageable)
- Maximum OAuth provider support is required
- Your team knows NextAuth and values ecosystem familiarity
- Simple auth needs (no 2FA, no orgs, no passkeys)
See our full comparison of boilerplates using Better Auth and the auth providers guide for SaaS. Browse boilerplates by auth provider on StarterPick.