NextAuth vs Clerk vs Supabase Auth
·StarterPick Team
authnextauthclerksupabase2026
TL;DR
- Clerk wins on developer experience and features (magic links, passkeys, organizations, MFA)
- Auth.js (NextAuth) wins on cost, control, and portability — but requires more setup
- Supabase Auth wins when you're already on Supabase and want zero extra services
For most SaaS boilerplates: if DX and time matter, use Clerk. If cost at scale matters, use Auth.js. If you're on Supabase already, use Supabase Auth.
Comparison
| Feature | Auth.js (NextAuth) | Clerk | Supabase Auth |
|---|---|---|---|
| Price | Free | Free (up to 10k MAU) → $25+/mo | Free (up to 50k MAU) → $25+/mo |
| Self-hosted | ✅ | ❌ | ✅ |
| Email/Password | ✅ | ✅ | ✅ |
| Magic Links | ✅ | ✅ | ✅ |
| OAuth Providers | 40+ | 20+ | 20+ |
| Passkeys | ⚠️ Limited | ✅ | ✅ |
| MFA/2FA | Manual | ✅ Built-in | ✅ Built-in |
| Organizations/Teams | Manual | ✅ Built-in | Manual |
| User Management UI | ❌ | ✅ Dashboard + embeddable | ✅ Dashboard |
| Session management | JWT / DB | JWT (opaque) | JWT |
| Bundle size | ~30KB | ~100KB+ | ~40KB |
| Edge Runtime | ✅ | ✅ | ✅ |
Auth.js (NextAuth v5) — Best Open Source
// auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import GitHub from 'next-auth/providers/github';
import Resend from 'next-auth/providers/resend';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { db } from '@/lib/db';
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(db),
providers: [
Google, GitHub,
Resend({ from: 'auth@yourapp.com' }), // Magic links via Resend
],
session: { strategy: 'database' }, // DB sessions vs JWT
callbacks: {
session: ({ session, user }) => ({
...session,
user: { ...session.user, id: user.id, role: user.role },
}),
},
});
Best for: Apps that need full control, can't afford $25+/month, or need to self-host.
Clerk — Best Developer Experience
// middleware.ts — Clerk auth middleware (one line)
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtected = createRouteMatcher(['/dashboard(.*)', '/api/(.*)']);
export default clerkMiddleware((auth, req) => {
if (isProtected(req)) auth().protect();
});
// app/dashboard/page.tsx
import { currentUser } from '@clerk/nextjs/server';
export default async function Dashboard() {
const user = await currentUser();
if (!user) redirect('/sign-in');
return <div>Hello {user.firstName}!</div>;
}
Best for: Speed-to-launch, teams that don't want to maintain auth code, products that need organizations/teams out of the box.
Supabase Auth — Best When Already on Supabase
// Client-side auth with Supabase
const { error } = await supabase.auth.signInWithPassword({
email, password,
});
// RLS automatically scoped to auth.uid()
// Database queries automatically isolated per user
const { data: documents } = await supabase
.from('documents')
.select('*');
// Returns only documents owned by current user (via RLS)
Best for: Apps using Supabase for the database — auth is integrated, RLS policies work with auth.uid().
Which Boilerplates Use Which
| Boilerplate | Auth Solution |
|---|---|
| ShipFast | Auth.js (NextAuth) |
| Supastarter | Supabase Auth |
| Makerkit | Supabase Auth or Clerk |
| T3 Stack | Auth.js |
| Epic Stack | Custom (Remix session) |
| Open SaaS | Wasp Auth (Auth.js underneath) |
The Organization/Team Decision
If your SaaS needs team accounts (B2B SaaS), this changes the auth choice:
- Clerk — Organizations built-in, $25+/month
- Auth.js — Add organization model yourself (~1-2 weeks)
- Supabase — Add organization model yourself (~1-2 weeks)
For B2B SaaS, Clerk's organization feature saves weeks of work. At early stage (<10k MAU), it's free.
Find boilerplates that include each auth provider on StarterPick.
Check out this boilerplate
View NextAuth (Auth.js) on StarterPick →