Clerk vs NextAuth in 2026: Managed vs Self-Hosted Auth
TL;DR
Clerk if you want to ship auth in 30 minutes; NextAuth if you need full control or can't justify the cost. Clerk (~1M weekly downloads) is a managed auth service with beautiful pre-built UI, social sign-in, MFA, and organizations out of the box. NextAuth/Auth.js (~2.5M downloads) is free, self-hosted, and highly customizable. Clerk's pricing starts free but scales to $25/month for 10,000 MAU. NextAuth is always free.
Key Takeaways
- NextAuth/Auth.js: ~2.5M weekly downloads — Clerk: ~1M (npm, March 2026)
- Clerk is fully managed — no database tables needed for auth
- Clerk pricing: free → $25+/month at scale — NextAuth is always free
- Clerk has built-in UI — embeddable sign-in/sign-up components
- NextAuth supports 80+ OAuth providers — Clerk supports major ones
Setup Comparison
The gap between setting up Clerk and NextAuth is significant enough to influence your choice before you write a single line of product code. Clerk's setup is intentionally minimal — you install the package, wrap your layout in <ClerkProvider>, add middleware, and set two environment variables. That's the entire auth surface area for a basic app.
NextAuth v5 (now officially Auth.js) requires more configuration: you create an auth.config.ts file, wire up the route handler, configure each OAuth provider with its own credentials, and typically connect a database adapter so that sessions and accounts are stored somewhere. If you want email/password login, you add a Credentials provider on top. This isn't difficult, but it's meaningfully more work than Clerk's approach — especially for developers who haven't done it before.
// Clerk — minimal setup, managed service
// 1. Install: npm install @clerk/nextjs
// 2. Add to app/layout.tsx:
import { ClerkProvider } from '@clerk/nextjs';
export default function RootLayout({ children }) {
return (
<ClerkProvider>
<html><body>{children}</body></html>
</ClerkProvider>
);
}
// 3. middleware.ts — protect routes
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtected = createRouteMatcher(['/dashboard(.*)']);
export default clerkMiddleware((auth, req) => {
if (isProtected(req)) auth().protect();
});
// 4. Environment variables (just 2):
// NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
// CLERK_SECRET_KEY=sk_live_...
// That's it — Clerk manages users, sessions, and auth state
// No database setup required for auth
// NextAuth v5 (Auth.js) — self-hosted, needs database adapter
// 1. Install: npm install next-auth @auth/drizzle-adapter
// 2. Create auth.config.ts:
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
import { DrizzleAdapter } from '@auth/drizzle-adapter';
import { db } from '@/db';
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DrizzleAdapter(db), // Stores sessions in your database
providers: [
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
session({ session, user }) {
session.user.id = user.id;
return session;
},
},
});
// 3. app/api/auth/[...nextauth]/route.ts
export { GET, POST } from '@/auth';
// 4. Environment variables (4+):
// AUTH_SECRET=...
// AUTH_GITHUB_ID=...
// AUTH_GITHUB_SECRET=...
// AUTH_GOOGLE_ID=...
// AUTH_GOOGLE_SECRET=...
// DATABASE_URL=...
// You must also run database migrations for the adapter tables
Clerk wins on time-to-working-auth by a wide margin. NextAuth wins when you want full visibility and control over your auth data — every session, every user, every token lives in your own database.
Hosted vs Self-Hosted Auth UI
One of the clearest practical differences between Clerk and NextAuth is who owns the sign-in and sign-up UI. Clerk ships <SignIn/> and <SignUp/> as embeddable React components. You drop them into a page and they render a fully styled, functional authentication form — email/password, magic links, social OAuth buttons, and MFA prompts are all handled. Clerk also provides hosted pages at /sign-in and /sign-up on a Clerk subdomain if you want to redirect there instead of embedding. The entire visual experience is configured from the Clerk dashboard, including colors, logo, and component layout.
NextAuth's approach is different by design. The default /api/auth/signin page is functional but deliberately plain — it's a starting point, not a final product. For a polished experience, you build your own sign-in page and call signIn('github') or signIn('credentials', { email, password }) from it. This gives you complete UI freedom, but you have to use that freedom. Most NextAuth apps end up building a custom auth page, which takes a few hours but produces exactly the UI you want.
// Clerk — beautiful pre-built components (Drop-in UI)
import { SignIn, SignUp, UserButton, UserProfile } from '@clerk/nextjs';
// Full sign-in page in one component:
export default function SignInPage() {
return <SignIn />;
// Includes: email/password, social login, MFA prompt
// Fully styled, customizable via Clerk dashboard theme
}
// User avatar + dropdown (profile, settings, sign out):
function Nav() {
return (
<nav>
<Logo />
<UserButton afterSignOutUrl="/" />
</nav>
);
}
// NextAuth — you build the UI yourself
'use client';
import { signIn, signOut, useSession } from 'next-auth/react';
function SignInPage() {
return (
<div>
<button onClick={() => signIn('github')}>Sign in with GitHub</button>
<button onClick={() => signIn('google')}>Sign in with Google</button>
{/* Build your own UI — complete freedom, complete responsibility */}
</div>
);
}
function Nav() {
const { data: session } = useSession();
return (
<nav>
<Logo />
{session ? (
<button onClick={() => signOut()}>Sign out ({session.user?.name})</button>
) : (
<button onClick={() => signIn()}>Sign in</button>
)}
</nav>
);
}
The practical implication: if you want a polished auth UI with zero design work, Clerk delivers it. If your brand requires a fully custom auth experience anyway, NextAuth saves you nothing on the UI side — but you still own the code and the data.
Organizations and Multi-Tenancy
Organizations are where Clerk's value proposition is most concentrated for B2B SaaS applications. Clerk has native Organizations built into the platform: team accounts, member management, role-based access, invitation emails, and an org switcher component. You get all of this without writing any database schema or backend logic. The useOrganization hook gives your UI access to the current org, its members, and the active role.
NextAuth has no concept of organizations. If your app needs multi-tenancy, you build it from scratch: an organizations table, a memberships table with roles, session augmentation to include the active org, an invitation system, and the UI for switching between orgs. This is typically two to four weeks of engineering work for a basic implementation, more for a robust one.
// Clerk — organizations built in
import { useOrganization, useOrganizationList } from '@clerk/nextjs';
function OrgSwitcher() {
const { organization } = useOrganization();
const { setActive, organizationList } = useOrganizationList();
return (
<select onChange={(e) => setActive({ organization: e.target.value })}>
{organizationList?.map(({ organization: org }) => (
<option key={org.id} value={org.id}>{org.name}</option>
))}
</select>
);
}
// Server-side: check org membership in middleware
import { auth } from '@clerk/nextjs/server';
export async function GET() {
const { orgId, orgRole } = auth();
if (orgRole !== 'org:admin') {
return new Response('Forbidden', { status: 403 });
}
// Handle admin-only request
}
// NextAuth — implement organizations yourself
// No built-in concept — you need all of this:
// 1. Database schema (example with Drizzle):
export const organizations = pgTable('organizations', {
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
slug: text('slug').unique().notNull(),
});
export const memberships = pgTable('memberships', {
userId: text('user_id').references(() => users.id),
orgId: uuid('org_id').references(() => organizations.id),
role: text('role').default('member'), // 'admin' | 'member'
});
// 2. Augment session to include active org
// 3. Build invitation system
// 4. Build org switcher UI
// Typical cost: 2-4 weeks of engineering
For a solo developer or small team building a multi-tenant SaaS, Clerk's organizations feature alone can justify the cost at early stages. For a larger team with engineering capacity, the NextAuth approach gives you more control over data model and pricing.
One nuance worth considering: Clerk's organization model uses roles, but the default roles are org:admin and org:member. If your product needs custom roles — owner, billing-manager, viewer, editor — Clerk supports custom roles on higher-tier plans, but you define and manage them through the Clerk dashboard rather than in code. NextAuth's manual approach means your roles are exactly what you put in the database, which gives you complete flexibility for complex permission models.
Middleware and Route Protection
Protecting routes is where Clerk's Next.js integration shines most clearly. The clerkMiddleware() function integrates directly with Next.js middleware, runs at the edge, and protects routes before the page or API handler even executes. You define route matchers once and every protected route is handled consistently. Clerk also provides auth().protect() for server components and currentUser() for server-side user access.
NextAuth's auth() function in v5 works similarly for server components and API routes — you import it and call it to get the session. For route-level protection in middleware, you add an authorized callback to your auth config and match routes with the middleware's matcher. Both approaches work, but Clerk's matcher pattern is slightly more ergonomic and the middleware integrates edge-first by design.
// Clerk — route protection patterns
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
import { auth, currentUser } from '@clerk/nextjs/server';
// Middleware: protect all /dashboard and /api/admin routes
const isProtected = createRouteMatcher(['/dashboard(.*)', '/api/admin(.*)']);
export default clerkMiddleware((auth, req) => {
if (isProtected(req)) auth().protect();
});
// Server component: get current user
async function DashboardPage() {
const user = await currentUser();
return <h1>Welcome, {user?.firstName}</h1>;
}
// API route: check auth
async function handler() {
const { userId } = auth();
if (!userId) return new Response('Unauthorized', { status: 401 });
}
// NextAuth v5 — route protection patterns
import { auth } from '@/auth';
// middleware.ts — protect routes
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname.startsWith('/dashboard')) {
return Response.redirect(new URL('/login', req.url));
}
});
// Server component: get session
async function DashboardPage() {
const session = await auth();
return <h1>Welcome, {session?.user?.name}</h1>;
}
// API route: check auth
async function handler() {
const session = await auth();
if (!session) return new Response('Unauthorized', { status: 401 });
}
Both patterns are clean and idiomatic for their respective libraries. The main difference is that Clerk's auth() gives you more user data by default (full user object, org info, role) while NextAuth's session shape is defined by your callbacks.
Pricing Reality
Clerk's pricing model is straightforward but its implications compound at scale. The free tier covers 10,000 MAU — generous for early-stage apps. Once you cross that threshold, you're on Clerk Pro at $25/month base plus $0.02 per MAU over 10K. For a growing B2C app, this becomes a meaningful line item.
Clerk pricing (March 2026):
Free tier: 10,000 MAU — $0/month
Pro: $25/month base + $0.02 per MAU over 10K
Organizations/B2B: included in Pro
Cost examples:
10,000 MAU: $0/month (free tier)
25,000 MAU: $25 + (15,000 × $0.02) = $325/month
50,000 MAU: $25 + (40,000 × $0.02) = $825/month
100,000 MAU: $25 + (90,000 × $0.02) = $1,825/month
NextAuth (Auth.js):
Always free — you pay only for your own database hosting
50,000 MAU cost: $0 auth + whatever Neon/PlanetScale charges (~$20-50/month)
100,000 MAU cost: $0 auth + same database hosting
At startup scale under 10K MAU, Clerk's free tier is a clear winner — you get enterprise-grade auth for free while you're finding product-market fit. Between 10K and 50K MAU, the decision depends on how much your team's time is worth relative to the monthly cost. Beyond 50K MAU, NextAuth's total cost advantage is hard to ignore unless Clerk's specific features (MFA, organizations, device sessions) are genuinely worth the difference.
There's also an indirect cost consideration: NextAuth requires you to provision and pay for a database if you don't already have one. A Neon or PlanetScale instance for auth sessions typically runs $20–50/month for moderate traffic. Factor that into the comparison, especially at small scale where Clerk's free tier plus zero database cost is genuinely competitive.
A related consideration is compliance. Clerk stores user PII — email addresses, phone numbers, device fingerprints — on Clerk's infrastructure. For apps serving EU users under GDPR, or for healthcare or financial applications with strict data residency requirements, that can be a problem regardless of cost. NextAuth stores everything in your own database, which puts you in full control of data location and retention. This is the deciding factor for some teams — not cost, but data sovereignty.
Package Health
| Metric | @clerk/nextjs | next-auth |
|---|---|---|
| Weekly downloads | ~1M | ~2.5M |
| GitHub stars | ~6k | ~24k |
| Latest release | Active, frequent | Active, v5 stable |
| Maintained | Yes — commercial | Yes — open-source |
| TypeScript | Full support | Full support |
| Next.js 14/15 support | Yes | Yes |
NextAuth's higher download count reflects its longer history and broader use across non-Next.js projects (Auth.js now supports SvelteKit, SolidStart, and others). Clerk is Next.js-primary, which is reflected in the tighter integration but smaller total download share.
When to Choose
Choose Clerk when:
- Speed to ship is paramount — auth in 30 minutes, not 3 days
- You need organizations/multi-tenancy without building from scratch
- Your app needs MFA, device sessions, or enterprise SSO
- You're under 10,000 MAU and the free tier covers you
- You value the hosted management dashboard and user impersonation tools
- You want a complete B2B auth solution without data-model decisions
Choose NextAuth/Auth.js when:
- Cost control is important — especially above 25K MAU
- You need full control over where user data lives
- GDPR or compliance requirements prevent sending user data to third parties
- Your OAuth provider needs aren't covered by Clerk (80+ providers via NextAuth)
- You prefer open-source solutions with no vendor dependency
- You're building a simple app with social login only — the complexity gap shrinks
For more on this comparison, see the full Clerk vs NextAuth package health breakdown and the detailed Auth0 vs Clerk comparison. If you're evaluating auth libraries for a new project, check the next-auth package page for current download trends and release history.
See the live comparison
View clerk vs. nextauth on PkgPulse →