L'authentification est le cœur de 89% des applications web modernes.
En 2026, deux solutions dominent l'écosystème Next.js : NextAuth.js (open-source, gratuit, flexible) et Clerk (SaaS premium, clé en main). Ce guide compare les deux sur 12 critères techniques et business.
Spoiler : Next-Auth pour control & budget. Clerk pour vitesse & UX premium.
TL;DR : Next-Auth ou Clerk ?
| Critère | Next-Auth v5 | Clerk | Gagnant |
|---|---|---|---|
| Prix | Gratuit (hosting seul) | 25€-299€/mois | 🏆 Next-Auth |
| Setup Time | 2-4 heures | 15-30 minutes | 🏆 Clerk |
| Customisation | ⭐⭐⭐⭐⭐ Totale | ⭐⭐⭐ Limitée | 🏆 Next-Auth |
| UI Components | À créer | ✅ Inclus (React) | 🏆 Clerk |
| OAuth Providers | 80+ (config manuelle) | 20+ (1-click) | 🏆 Clerk (UX) |
| Passkeys/WebAuthn | ✅ Full control | ✅ Built-in | ⚖️ Draw |
| MFA | Code custom | ✅ Built-in (SMS, TOTP) | 🏆 Clerk |
| User Management | Database custom | ✅ Dashboard admin | 🏆 Clerk |
| Performance | Excellent (self-hosted) | Excellent (CDN global) | ⚖️ Draw |
| Sécurité | ⭐⭐⭐⭐⭐ (si bien config) | ⭐⭐⭐⭐⭐ (managed) | ⚖️ Draw |
| Vendor Lock-in | ✅ Aucun | ⚠️ Fort | 🏆 Next-Auth |
| Docs Quality | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 🏆 Clerk |
Score final : Next-Auth 7/12, Clerk 7/12, Draw 4/12
Architecture Système : Comment Fonctionnent-ils ?
Next-Auth v5 (Architecture)
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │ ────▶ │ Next.js API │ ────▶ │ Database │
│ │ │ /api/auth/* │ │ (Postgres) │
└─────────────┘ └──────────────┘ └──────────────┘
│
▼
┌──────────────┐
│ OAuth Provider│
│ (Google, etc)│
└──────────────┘
Flow :
- User clique "Login with Google"
- Next.js API route
/api/auth/signin/googleredirect vers Google - Google callback
/api/auth/callback/google - Next-Auth crée session + JWT
- Session stockée database + cookie
- User authentifié
Clerk (Architecture)
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │ ────▶ │ Clerk API │ ────▶ │ Clerk Cloud │
│ <ClerkProvider> │ (clerk.com) │ │ Database │
└─────────────┘ └──────────────┘ └──────────────┘
│
▼
┌────────────────┐
│ Clerk Components│
│ <SignIn /> │
│ <UserButton /> │
└────────────────┘
Flow :
- User interagit avec
<SignIn />(Clerk component) - Clerk SDK appelle Clerk API (clerk.com)
- OAuth handled par Clerk servers
- Session JWT retourné (signed by Clerk)
- Next.js vérifie JWT via Clerk SDK
- User authentifié
Différence clé : Next-Auth = self-hosted, Clerk = SaaS managed
Implémentation Next-Auth v5
Setup Complet (30 minutes)
1. Installation
pnpm add next-auth@beta @auth/drizzle-adapter drizzle-orm
pnpm add -D drizzle-kit
Note : Next-Auth v5 = "Auth.js" (rebrand)
2. Database Schema (Drizzle ORM)
// db/schema.ts
import { pgTable, text, timestamp, primaryKey, integer } from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'
export const users = pgTable('users', {
id: text('id')
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: text('name'),
email: text('email').unique().notNull(),
emailVerified: timestamp('email_verified', { mode: 'date' }),
image: text('image'),
createdAt: timestamp('created_at').defaultNow(),
})
export const accounts = pgTable(
'accounts',
{
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
type: text('type').notNull(),
provider: text('provider').notNull(),
providerAccountId: text('provider_account_id').notNull(),
refresh_token: text('refresh_token'),
access_token: text('access_token'),
expires_at: integer('expires_at'),
token_type: text('token_type'),
scope: text('scope'),
id_token: text('id_token'),
session_state: text('session_state'),
},
(account) => ({
compoundKey: primaryKey({ columns: [account.provider, account.providerAccountId] }),
})
)
export const sessions = pgTable('sessions', {
sessionToken: text('session_token').primaryKey(),
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
expires: timestamp('expires', { mode: 'date' }).notNull(),
})
export const verificationTokens = pgTable(
'verification_tokens',
{
identifier: text('identifier').notNull(),
token: text('token').notNull(),
expires: timestamp('expires', { mode: 'date' }).notNull(),
},
(vt) => ({
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
})
)
// Relations
export const usersRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
sessions: many(sessions),
}))
3. Auth Configuration
// auth.ts (root)
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import GitHub from 'next-auth/providers/github'
import { DrizzleAdapter } from '@auth/drizzle-adapter'
import { db } from '@/db'
import { users, accounts, sessions, verificationTokens } from '@/db/schema'
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DrizzleAdapter(db, {
usersTable: users,
accountsTable: accounts,
sessionsTable: sessions,
verificationTokensTable: verificationTokens,
}),
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
allowDangerousEmailAccountLinking: true, // Auto-link same email
}),
GitHub({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
session: {
strategy: 'database', // ou 'jwt' pour stateless
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/login',
signOut: '/logout',
error: '/auth/error',
verifyRequest: '/auth/verify-request',
},
callbacks: {
async session({ session, user }) {
// Add user ID to session
if (session.user) {
session.user.id = user.id
}
return session
},
async jwt({ token, user }) {
if (user) {
token.id = user.id
}
return token
},
},
debug: process.env.NODE_ENV === 'development',
})
4. API Route Handler
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers
5. Middleware (Route Protection)
// middleware.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'
export default auth((req) => {
const { pathname } = req.nextUrl
const isAuthenticated = !!req.auth
// Protected routes
const protectedRoutes = ['/dashboard', '/profile', '/settings']
const isProtectedRoute = protectedRoutes.some((route) => pathname.startsWith(route))
if (isProtectedRoute && !isAuthenticated) {
return NextResponse.redirect(new URL('/login', req.url))
}
// Redirect authenticated users away from login
if (pathname === '/login' && isAuthenticated) {
return NextResponse.redirect(new URL('/dashboard', req.url))
}
return NextResponse.next()
})
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
6. Login Page
// app/login/page.tsx
import { signIn } from '@/auth'
import { Button } from '@/components/ui/button'
export default function LoginPage() {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md space-y-4 rounded-lg border p-8">
<h1 className="text-2xl font-bold">Sign In</h1>
<form
action={async () => {
'use server'
await signIn('google', { redirectTo: '/dashboard' })
}}
>
<Button type="submit" className="w-full">
Continue with Google
</Button>
</form>
<form
action={async () => {
'use server'
await signIn('github', { redirectTo: '/dashboard' })
}}
>
<Button type="submit" variant="outline" className="w-full">
Continue with GitHub
</Button>
</form>
</div>
</div>
)
}
7. Protected Page (Server Component)
// app/dashboard/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { LogoutButton } from '@/components/logout-button'
export default async function DashboardPage() {
const session = await auth()
if (!session) {
redirect('/login')
}
return (
<div className="p-8">
<h1>Dashboard</h1>
<p>Welcome, {session.user?.name}!</p>
<p>Email: {session.user?.email}</p>
<img src={session.user?.image || ''} alt="Avatar" className="h-12 w-12 rounded-full" />
<LogoutButton />
</div>
)
}
8. Logout Component (Client)
// components/logout-button.tsx
'use client'
import { signOut } from 'next-auth/react'
import { Button } from '@/components/ui/button'
export function LogoutButton() {
return <Button onClick={() => signOut({ redirectTo: '/' })}>Sign Out</Button>
}
Setup complet : ~2 heures (avec database schema)
Implémentation Clerk
Setup Complet (15 minutes)
1. Création Compte Clerk
- Signup sur clerk.com
- Créer nouvelle application
- Choisir OAuth providers (Google, GitHub, etc.)
- Copier API keys
2. Installation
pnpm add @clerk/nextjs
3. Environment Variables
# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
CLERK_SECRET_KEY=sk_test_xxx
# Redirect URLs
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
4. Root Layout Wrapper
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs'
import { frFR } from '@clerk/localizations'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider localization={frFR}>
<html lang="fr">
<body>{children}</body>
</html>
</ClerkProvider>
)
}
5. Middleware (Route Protection)
// middleware.ts
import { authMiddleware } from '@clerk/nextjs'
export default authMiddleware({
publicRoutes: ['/', '/blog(.*)'],
ignoredRoutes: ['/api/webhook'],
})
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'],
}
6. Sign In Page
// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from '@clerk/nextjs'
export default function SignInPage() {
return (
<div className="flex min-h-screen items-center justify-center">
<SignIn
appearance={{
elements: {
rootBox: 'mx-auto',
card: 'shadow-lg',
},
}}
/>
</div>
)
}
7. Protected Page
// app/dashboard/page.tsx
import { currentUser } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const user = await currentUser()
if (!user) {
redirect('/sign-in')
}
return (
<div className="p-8">
<h1>Dashboard</h1>
<p>
Welcome, {user.firstName} {user.lastName}!
</p>
<p>Email: {user.emailAddresses[0].emailAddress}</p>
<img src={user.imageUrl} alt="Avatar" className="h-12 w-12 rounded-full" />
</div>
)
}
8. User Button (Client Component)
// components/header.tsx
import { UserButton } from '@clerk/nextjs'
export function Header() {
return (
<header className="border-b">
<div className="container flex h-16 items-center justify-between">
<h1>My App</h1>
<UserButton
afterSignOutUrl="/"
appearance={{
elements: {
avatarBox: 'h-10 w-10',
},
}}
/>
</div>
</header>
)
}
Setup complet : ~15 minutes (sans database schema car managed by Clerk)
Comparatif Détaillé
1. Pricing (Coûts Réels)
Next-Auth
Coûts :
- Library : Gratuit (open-source)
- Hosting database : 0-50€/mois (Neon, Supabase)
- Total : 0-50€/mois
Scaling :
- 10k users : ~10€/mois
- 100k users : ~30€/mois
- 1M users : ~150€/mois (database scaling)
Clerk
Plans 2026 :
- Free : 10k MAUs (Monthly Active Users) gratuit
- Pro : 25€/mois (50k MAUs inclus)
- Enterprise : 299€/mois+ (custom)
Overage pricing :
- +0.02€ par MAU supplémentaire
Scaling :
- 10k users : Gratuit
- 50k users : 25€/mois
- 100k users : 25€ + (50k × 0.02€) = 1,025€/mois
- 1M users : ~20,000€/mois
ROI Comparison (100k users) :
- Next-Auth : ~30€/mois
- Clerk : ~1,025€/mois
Économie Next-Auth : 995€/mois (33× moins cher)
2. Features Comparison
| Feature | Next-Auth v5 | Clerk |
|---|---|---|
| OAuth (Google, GitHub, etc.) | ✅ 80+ providers | ✅ 20+ providers |
| Email/Password | ✅ (custom) | ✅ Built-in |
| Magic Links | ✅ (custom) | ✅ Built-in |
| Passkeys/WebAuthn | ✅ Experimental | ✅ Production |
| MFA (2FA) | ✅ Custom code | ✅ SMS, TOTP, Backup codes |
| Phone Authentication | ❌ Custom | ✅ Built-in (SMS) |
| User Management UI | ❌ À créer | ✅ Dashboard admin |
| Profile Management | ❌ À créer | ✅ <UserProfile /> component |
| Organization/Teams | ❌ Custom | ✅ Built-in |
| Role-based Access (RBAC) | ✅ Custom | ✅ Built-in |
| Audit Logs | ❌ Custom | ✅ Enterprise plan |
| Webhooks | ❌ Custom | ✅ Built-in |
| Session Management | ✅ Database ou JWT | ✅ JWT (managed) |
| Analytics | ❌ Custom | ✅ Dashboard |
Winner Features : Clerk (built-in vs custom development)
3. Developer Experience
Setup Time
- Next-Auth : 2-4 heures (database schema, UI custom)
- Clerk : 15-30 minutes (tout built-in)
Winner : Clerk (8× plus rapide)
Customisation UI
Next-Auth :
// ✅ Control total design
<Button onClick={() => signIn('google')}>
<GoogleIcon />
Continue with Google
</Button>
Clerk :
// ⚠️ Customisation limitée (Appearance API)
<SignIn
appearance={{
elements: {
card: 'custom-class', // Tailwind classes
rootBox: 'mx-auto',
},
variables: {
colorPrimary: '#000000',
},
}}
/>
Winner : Next-Auth (design 100% custom)
Type Safety
Next-Auth :
// ✅ Full TypeScript control
declare module 'next-auth' {
interface Session {
user: {
id: string
role: 'admin' | 'user'
subscription: 'free' | 'pro'
} & DefaultSession['user']
}
}
Clerk :
// ✅ Types built-in
const user = await currentUser()
// user.id, user.firstName, etc. auto-typed
Winner : Draw (both excellent)
4. Performance
Next-Auth (Self-Hosted)
Latency :
- Session check : 15-30ms (database query)
- JWT verify : 1-2ms (if JWT strategy)
Optimizations :
// Edge-compatible JWT strategy
export const authConfig = {
session: { strategy: 'jwt' }, // ✅ No database query
}
Clerk (SaaS)
Latency :
- Session check : 20-40ms (Clerk API call)
- JWT verify : 5-10ms (local w/ public key)
CDN Global :
- Clerk servers replicated globally
- Edge locations : 200+
Winner : Next-Auth (JWT) (1-2ms vs 5-10ms)
5. Sécurité
Next-Auth
Responsabilités :
- ✅ CSRF protection (built-in)
- ✅ Session encryption
- ⚠️ OAuth config secure (manual)
- ⚠️ Database security (votre responsabilité)
Security Checklist :
// ✅ Secure cookies
export const authConfig = {
cookies: {
sessionToken: {
name: '__Secure-next-auth.session-token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: true, // HTTPS only
},
},
},
}
Clerk
Managed Security :
- ✅ SOC 2 Type II compliant
- ✅ GDPR compliant
- ✅ CCPA compliant
- ✅ Automatic security updates
- ✅ DDoS protection
- ✅ Rate limiting
Winner : Clerk (security managed, certifications)
Use Cases : Quand Choisir Quoi ?
Choisir Next-Auth Si :
✅ Budget serré (<25€/mois auth acceptable) ✅ Scaling >100k users (économies massives) ✅ Control total data (GDPR strict, data sovereignty) ✅ Customisation UI totale (design system custom) ✅ Pas vendor lock-in (migration future facile) ✅ Déjà database (Postgres, MySQL)
Exemples projets :
- SaaS B2B (data sovereignty EU)
- Startups budget limité
- Apps >100k users (scaling)
- Open-source projects
Choisir Clerk Si :
✅ Time-to-market critique (<1 semaine setup) ✅ Pas équipe backend (product team seulement) ✅ Features avancées needed (MFA, Organizations, Phone auth) ✅ Budget confortable (<100k users, 25-299€/mois OK) ✅ Compliance managée (SOC 2, GDPR délégué)
Exemples projets :
- MVP startups (launch rapide)
- Agency client projects (livraison rapide)
- B2C apps (<50k users)
- No-code/low-code platforms
Migration Next-Auth → Clerk (ou inverse)
Clerk → Next-Auth
Difficulté : 🟡 Moyenne
Steps :
- Export users via Clerk API
- Créer database schema Next-Auth
- Import users (hash passwords)
- Replace
<ClerkProvider>par Next-Auth - Update tous
currentUser()calls
Timeline : 2-3 jours
Next-Auth → Clerk
Difficulté : 🟢 Facile
Steps :
- Créer app Clerk
- Import users via Clerk Backend API
- Replace Next-Auth par
<ClerkProvider> - Update authentication checks
Timeline : 1 jour
Vendor lock-in warning : Clerk migration inverse = plus difficile (data export limité)
Benchmark Performance Réel
Test Setup
App : Next.js 15, 10k users simulés Server : Vercel Pro Database : Neon (Postgres)
Résultats
| Métrique | Next-Auth (JWT) | Next-Auth (DB) | Clerk |
|---|---|---|---|
| Session check (p50) | 1.2ms | 28ms | 8ms |
| Session check (p99) | 3ms | 85ms | 22ms |
| Login latency | 420ms | 450ms | 380ms |
| Bundle size (client) | 12KB | 12KB | 45KB |
| Cold start Impact | +15ms | +40ms | +25ms |
Analysis :
- Next-Auth JWT : Fastest session checks (no DB)
- Clerk : Best login UX (optimized flow)
- Next-Auth DB : Slowest (database query overhead)
Winner : Next-Auth (JWT strategy) pour performance pure
Coûts Développement (ROI)
Next-Auth
Setup initial :
- Database schema : 1h
- Auth config : 2h
- Pages UI (login, signup) : 4h
- Testing : 2h
- Total : 9 heures (6,300€ @ 700€ TJM)
Maintenance annuelle :
- Security updates : 2h/an
- Bug fixes : 4h/an
- Total : 6h/an (4,200€/an)
Coût 3 ans :
- Setup : 6,300€
- Maintenance : 12,600€
- Hosting : 1,800€ (50€/mois × 36)
- Total : 20,700€
Clerk
Setup initial :
- Installation : 15min
- Config : 15min
- Testing : 30min
- Total : 1 heure (700€ @ 700€ TJM)
Subscription 3 ans :
- Pro plan : 25€/mois × 36 = 900€
- Overage (avg 30k MAUs) : 0€
- Total : 900€
Coût 3 ans :
- Setup : 700€
- Subscription : 900€
- Total : 1,600€
Économie Clerk : 19,100€ (3 ans)
Mais : Si scaling >100k users, Next-Auth redevient rentable
Break-Even Point
Calcul :
Clerk devient plus cher quand :
Next-Auth Total Cost < Clerk Total Cost
Setup (6,300€) + Hosting (50€/mois) < Clerk Subscription + Overages
6,300 + (50 × months) < (25 × months) + (MAUs - 50k) × 0.02
Break-even : ~80k MAUs (Monthly Active Users)
Conclusion ROI :
- <80k MAUs : Clerk plus rentable (dev time économisé)
- >80k MAUs : Next-Auth plus rentable (scale costs)
Recommendation 2026
Par Profil Projet
Startups Pré-Seed/Seed :
- 🏆 Clerk (time-to-market > cost)
- Économie 8h dev (5,600€)
- Permet focus features core business
Startups Serie A+ :
- 🏆 Next-Auth (scaling costs)
- À 100k users : économie 1,000€/mois
- ROI annuel : 12,000€
Entreprises (B2B SaaS) :
- 🏆 Next-Auth (data sovereignty)
- GDPR compliance control
- Pas vendor lock-in
Agencies (Client Projects) :
- 🏆 Clerk (livraison rapide)
- Setup 15min vs 9h
- Client-friendly admin dashboard
Open-Source Projects :
- 🏆 Next-Auth (gratuit, self-hosted)
- Community-driven
- Pas dépendance SaaS
Notre Choix Hulli Studio
Projets clients : Clerk (95% cas)
- Livraison rapide
- Client admin dashboard
- Support managed
Projets internes : Next-Auth
- Control total
- Customisation poussée
- Pas récurrent costs
Conclusion
Next-Auth et Clerk sont excellents. Le choix dépend de :
- Budget : Next-Auth si >80k users
- Time : Clerk si launch <2 semaines
- Control : Next-Auth si data sovereignty
- Features : Clerk si MFA/Organizations needed
Notre top pick 2026 : Clerk pour 80% projets
- Setup 8× plus rapide
- UX premium
- ROI positif <80k users
Exceptions (Next-Auth) :
- Scaling >100k users
- GDPR strict (healthcare, finance)
- Budget dev confortable
FAQ
Clerk compatible Next.js 15 App Router ?
✅ Oui, full support. Clerk SDK optimisé pour Server Components avec currentUser() async.
Next-Auth v5 stable ?
✅ Oui depuis janvier 2025. Production-ready, utilisé par Vercel, Cal.com, etc.
Peut-on migrer Clerk → Next-Auth après ?
⚠️ Oui mais difficile. Export users Clerk API, re-hash passwords, update code. Timeline 2-3 jours. Vendor lock-in modéré.
Next-Auth support Passkeys ?
✅ Oui (experimental). Features WebAuthn depuis v5. Pas encore production-grade comme Clerk.
Articles connexes :