Better Auth vs Clerk vs NextAuth
·StarterPick Team
better-authclerknextauthauthenticationsaasnext-js2026
TL;DR
Better Auth is the new third option in Next.js authentication — more feature-complete than NextAuth, free like NextAuth, but easier to set up. It ships with built-in email/password, OAuth, magic links, two-factor auth, and passkeys out of the box. NextAuth v5 (Auth.js) is the established alternative with the larger ecosystem. Clerk is the managed option where you pay for convenience. Better Auth sits in the sweet spot: self-hosted (no vendor lock-in), batteries-included (no building auth UIs from scratch), and actively maintained.
Key Takeaways
- Better Auth: new (2024-2025), self-hosted, batteries-included, free, built-in 2FA/passkeys/organizations
- NextAuth v5: established, self-hosted, free, requires more manual setup, huge ecosystem
- Clerk: managed, paid ($0.02/MAU after 10K), fastest setup, best UX, organizations built-in
- Break-even: Clerk becomes expensive vs free options at ~5,000-10,000 active users
- Better Auth vs NextAuth: Better Auth wins on built-in features; NextAuth wins on ecosystem/maturity
- In boilerplates: T3 uses NextAuth, Supastarter uses Clerk, Better Auth gaining adoption in 2025+
Better Auth: The New Contender
npm install better-auth
Setup
// lib/auth.ts
import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { twoFactor, passkey, organization, magicLink } from 'better-auth/plugins';
import { db } from './db';
export const auth = betterAuth({
database: prismaAdapter(db, {
provider: 'postgresql',
}),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
sendVerificationEmail: async ({ user, token }) => {
await sendVerificationEmail(user.email, token);
},
sendResetPassword: async ({ user, token }) => {
await sendPasswordResetEmail(user.email, token);
},
},
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({
totpIssuer: 'My SaaS', // TOTP/authenticator app 2FA
}),
passkey(), // WebAuthn passkeys — zero config!
magicLink({
sendMagicLink: async ({ email, token }) => {
await sendMagicLinkEmail(email, token);
},
}),
organization({
// Multi-tenant organizations — built-in
}),
],
});
export type Session = typeof auth.$Infer.Session;
// app/api/auth/[...all]/route.ts
import { auth } from '@/lib/auth';
import { toNextJsHandler } from 'better-auth/next-js';
export const { GET, POST } = toNextJsHandler(auth);
Client Usage
// Client-side:
import { createAuthClient } from 'better-auth/react';
import { twoFactorClient, passkeyClient } from 'better-auth/client/plugins';
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL,
plugins: [twoFactorClient(), passkeyClient()],
});
export const { useSession, signIn, signOut, signUp } = authClient;
// Sign-in component:
export function SignIn() {
return (
<div>
{/* Email/password: */}
<button onClick={() => signIn.emailAndPassword({ email, password })}>
Sign In
</button>
{/* OAuth: */}
<button onClick={() => signIn.social({ provider: 'google', callbackURL: '/dashboard' })}>
Continue with Google
</button>
{/* Magic link: */}
<button onClick={() => signIn.magicLink({ email, callbackURL: '/dashboard' })}>
Send Magic Link
</button>
{/* Passkey: */}
<button onClick={() => signIn.passkey()}>
Sign in with Passkey
</button>
</div>
);
}
Feature Comparison
| Feature | Better Auth | NextAuth v5 | Clerk |
|---|---|---|---|
| Email/password | ✅ Built-in | ✅ Credentials | ✅ |
| OAuth | ✅ | ✅ | ✅ 30+ |
| Magic links | ✅ Plugin | ❌ (manual) | ✅ |
| Two-factor (TOTP) | ✅ Plugin | ❌ (manual) | ✅ |
| Passkeys | ✅ Plugin | ❌ | ✅ |
| Organizations | ✅ Plugin | ❌ (manual) | ✅ |
| Session management | ✅ | ✅ | ✅ |
| Pre-built UI | ❌ | ❌ | ✅ |
| Self-hosted | ✅ | ✅ | ❌ |
| Vendor lock-in | None | None | High |
| Price | Free | Free | Free to 10K MAU |
| Maturity | 2024/new | 2020/mature | 2022/mature |
When to Choose Each
Choose Better Auth if:
→ Self-hosted with more features than NextAuth
→ Need 2FA, passkeys, or magic links without building them
→ Starting a new project and want modern auth DX
→ Don't want vendor lock-in
Choose NextAuth v5 if:
→ Established codebase already using it
→ Prefer maximum ecosystem maturity
→ Using non-Prisma ORMs (more adapters available)
→ Team has existing NextAuth expertise
Choose Clerk if:
→ Speed is priority (30-minute setup vs 2-3 hours)
→ Building B2B SaaS needing organizations now
→ Budget is not a concern at current scale
→ Need the best pre-built auth UI
Find boilerplates using Better Auth, Clerk, and NextAuth at StarterPick.