Skip to main content

Clerk vs NextAuth v5 (2026)

·APIScout Team
Share:

TL;DR

Clerk if you want auth done in 30 minutes and don't mind paying. NextAuth v5 if you want control and the $0 cost at any scale. Clerk provides a full auth UI, user management dashboard, organizations, MFA, and device sessions out of the box — but at $0.02/MAU after 10K users, it gets expensive. NextAuth v5 (now called Auth.js) handles the hard parts of OAuth but requires you to build the user management UI. The break-even where Clerk becomes expensive: around 5,000-10,000 monthly active users.

Key Takeaways

  • Clerk: hosted auth + UI components, $0 free to 10K MAU then $0.02/MAU, fastest setup
  • NextAuth v5 (Auth.js): self-hosted, free, requires Postgres adapter, requires custom UI
  • Clerk advantages: Organizations, device sessions, MFA, user impersonation, JWT templates
  • NextAuth advantages: Full control, zero vendor lock-in, free at any scale, self-hostable
  • Break-even: Clerk becomes expensive vs NextAuth at ~5,000 active users ($100/month)
  • Migration difficulty: switching auth providers is painful (passwords can't be migrated)

Clerk: Full-Service Auth

Best for: teams that want auth to "just work", B2B SaaS needing organizations, quick prototypes

npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm install @clerk/nextjs
// app/layout.tsx — wrap your app:
import { ClerkProvider } from '@clerk/nextjs';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  );
}
// middleware.ts — protect routes:
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher([
  '/',
  '/sign-in(.*)',
  '/sign-up(.*)',
  '/pricing',
  '/api/webhooks(.*)',  // Webhooks are public
]);

export default clerkMiddleware(async (auth, request) => {
  if (!isPublicRoute(request)) {
    await auth.protect();  // Redirects to sign-in if not authenticated
  }
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
// app/dashboard/page.tsx — server component with user:
import { auth, currentUser } from '@clerk/nextjs/server';

export default async function Dashboard() {
  const { userId } = await auth();
  const user = await currentUser();

  return (
    <div>
      <h1>Hello, {user?.firstName}!</h1>
      <p>User ID: {userId}</p>
    </div>
  );
}
// Client component:
'use client';
import { useUser, useClerk, SignOutButton } from '@clerk/nextjs';

export function UserMenu() {
  const { user } = useUser();
  const { openUserProfile } = useClerk();

  return (
    <div>
      <img src={user?.imageUrl} alt={user?.fullName ?? 'User'} />
      <button onClick={() => openUserProfile()}>Profile</button>
      <SignOutButton>Sign Out</SignOutButton>
    </div>
  );
}

Clerk Organizations (B2B Multi-Tenancy)

// Built-in org support — no extra code needed:
import { auth } from '@clerk/nextjs/server';

export default async function OrgDashboard() {
  const { orgId, orgRole, userId } = await auth();

  if (!orgId) redirect('/select-org');

  // Org-scoped queries:
  const projects = await db.project.findMany({
    where: { organizationId: orgId },  // Store orgId in your DB
  });
}

Clerk Pricing

Free:          10,000 MAU
Pro:           $0.02/MAU after free tier
               + $25/month base

5,000 MAU:   $25/month (all in free tier)
10,000 MAU:  $25/month (all in free tier)
20,000 MAU:  $25 + (10,000 × $0.02) = $225/month
50,000 MAU:  $25 + (40,000 × $0.02) = $825/month

For organizations (B2B):
  $25/month for Clerk Teams (unlocks org features)

NextAuth v5 (Auth.js): Self-Hosted

Best for: cost at scale, full control, teams comfortable with configuration

npm install next-auth@beta @auth/prisma-adapter
// auth.ts — configure providers:
import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcryptjs';
import { db } from '@/lib/db';

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(db),
  providers: [
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }),
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    Credentials({
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      authorize: async (credentials) => {
        const user = await db.user.findUnique({
          where: { email: credentials.email as string },
        });
        if (!user?.passwordHash) return null;

        const valid = await bcrypt.compare(
          credentials.password as string,
          user.passwordHash
        );
        return valid ? user : null;
      },
    }),
  ],
  session: { strategy: 'jwt' },  // or 'database' for server-side sessions
  pages: {
    signIn: '/auth/sign-in',
    error: '/auth/error',
  },
  callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        token.userId = user.id;
        token.plan = (user as any).plan ?? 'free';
      }
      return token;
    },
    session: async ({ session, token }) => {
      session.user.id = token.userId as string;
      session.user.plan = token.plan as string;
      return session;
    },
  },
});
// app/api/auth/[...nextauth]/route.ts:
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
// middleware.ts — protect routes:
import { auth } from '@/auth';
import { NextResponse } from 'next/server';

export default auth((req) => {
  const isAuthenticated = !!req.auth;
  const isAuthPage = req.nextUrl.pathname.startsWith('/auth');
  const isPublicPage = ['/', '/pricing', '/about'].includes(req.nextUrl.pathname);

  if (!isAuthenticated && !isAuthPage && !isPublicPage) {
    const signInUrl = new URL('/auth/sign-in', req.url);
    signInUrl.searchParams.set('callbackUrl', req.nextUrl.pathname);
    return NextResponse.redirect(signInUrl);
  }

  return NextResponse.next();
});

export const config = { matcher: ['/((?!api/auth|_next/static|_next/image|.*\\.png$).*)'] };

Prisma Schema (Required for Database Sessions)

// prisma/schema.prisma — NextAuth requires these tables:
model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String    @unique
  emailVerified DateTime?
  image         String?
  passwordHash  String?   // For credentials auth
  plan          String    @default("free")
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

Build Your Own UI

NextAuth requires custom UI (no pre-built components):

// app/auth/sign-in/page.tsx:
'use client';
import { signIn } from 'next-auth/react';
import { useState } from 'react';

export default function SignInPage() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="w-96 space-y-4">
        <h1 className="text-2xl font-bold">Sign In</h1>

        {/* Social providers: */}
        <button
          onClick={() => signIn('github', { callbackUrl: '/dashboard' })}
          className="w-full border p-2"
        >
          Continue with GitHub
        </button>
        <button
          onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
          className="w-full border p-2"
        >
          Continue with Google
        </button>

        <hr />

        {/* Email/password: */}
        <form
          onSubmit={async (e) => {
            e.preventDefault();
            await signIn('credentials', { email, password, callbackUrl: '/dashboard' });
          }}
        >
          <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
          <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
          <button type="submit">Sign In</button>
        </form>
      </div>
    </div>
  );
}

Side-by-Side Comparison

ClerkNextAuth v5
Setup time30 minutes2-3 hours
Pre-built UI✅ Full UI components❌ Build yourself
User management dashboard❌ Build yourself
Organizations/Teams✅ Built-in❌ Build yourself
MFA✅ Built-in❌ Custom implementation
Device sessions
Vendor lock-inHighNone
Self-hostable
Price at 10K usersFreeFree
Price at 50K users~$825/month$0 (infra only)
TypeScript supportExcellentGood
OAuth providers30+50+

When to Choose Each

Choose CLERK if:
  → Building a B2B SaaS with organizations
  → Speed matters more than cost control
  → You need MFA, device sessions, user impersonation without building it
  → Budget is not a concern at current scale
  → You want a slick user management UI for free

Choose NEXTAUTH v5 if:
  → Expected to grow to 20K+ users ($400+/month in Clerk)
  → Compliance requires you to control auth infrastructure
  → Building on-premise / self-hosted
  → Your team is comfortable with more initial setup
  → You want zero vendor lock-in (switch providers easily)

Authentication Flow and Session Model

Understanding how each tool manages sessions shapes the security properties and performance characteristics of your application in ways that aren't obvious from the quick-start docs.

Clerk uses server-side sessions stored in Clerk's infrastructure. When a user signs in, Clerk issues a short-lived JWT that your application uses to verify identity. On every request, Clerk validates the session token against its own servers — this means one network roundtrip to Clerk per authenticated request in the default configuration. The upside of this model is that session revocation is instant: if you revoke a session from the Clerk dashboard (or your code calls the revocation API), the user is logged out on their next request. There is no window where a revoked session can still be used. This is particularly important for enterprise use cases where you need to terminate access immediately — when an employee is offboarded, for example.

NextAuth v5 supports two session strategies: database sessions and JWT sessions. With database sessions, session data is stored in your Postgres table and validated on every request, similar to Clerk's model. With JWT sessions (the default), the session is encoded in a signed cookie and validated stateless — no database query or network call required on each request. JWT sessions are faster per request and simpler to scale, but they cannot be revoked until the token expires. If your JWT session lifetime is 30 days, a compromised or revoked-on-the-server JWT will still work for up to 30 days. For most web applications this is an acceptable tradeoff, but for security-sensitive applications — healthcare, finance, anything with sensitive user data — it's worth using database sessions.

For Next.js App Router specifically, both tools support React Server Components. Clerk exposes an auth() function you import from @clerk/nextjs/server. NextAuth v5 also exports an auth() function from your auth configuration file. The function name is identical, which creates real confusion when reading tutorials or switching between the two — always check the import source to know which one you're calling.

Pricing and Scaling

The cost comparison between Clerk and NextAuth is straightforward on paper but requires some thought about your specific user growth trajectory.

Clerk's pricing is entirely MAU-based (monthly active users, meaning users who signed in at least once that month). The free tier covers 10,000 MAU. The Pro plan adds a $25/month base and charges $0.02 per MAU above the free tier. The math compounds quickly: 20,000 MAU costs $225/month, 50,000 MAU costs $825/month, and 100,000 MAU costs $1,825/month. For a B2B SaaS where each active user is a paying customer generating meaningful revenue, the $0.02/MAU charge is a negligible line item. For a consumer app where the majority of users are on a free tier, the cost becomes harder to justify.

NextAuth v5 is open source and free to use at any scale. Your costs are the underlying infrastructure: a Postgres database for session storage (typically $10-25/month on Neon, Railway, or Supabase for a production database with reasonable specs), and the OAuth provider costs — Google, GitHub, and most social providers are free for standard OAuth. Enterprise SSO providers like Okta or Azure AD have their own licensing costs, but those are separate from NextAuth itself.

The total cost of ownership calculation is more nuanced than a simple dollar comparison. Clerk's price includes the engineering time you don't spend building auth UI, the security maintenance you don't own, compliance features like SOC 2 Type II certification, GDPR tooling, and access to Clerk's support team when something breaks. NextAuth is cheaper but you own the maintenance burden — keeping dependencies updated, responding to security vulnerabilities, building the password reset flow, the email verification flow, and any MFA you need. For a solo founder or small team, that maintenance overhead has real cost even if it's not a dollar line item.

For B2B SaaS where each user represents meaningful revenue, Clerk's per-MAU pricing is typically negligible and the time savings are substantial. For consumer apps with millions of free users, NextAuth or a self-hosted auth solution is the financially sound choice.

When to Choose Each in Practice

The high-level decision framework holds up well in practice, but there are some nuances worth spelling out.

Choose Clerk if you're building a B2B SaaS where organizations and multi-tenancy are core requirements. Clerk's built-in organization model — with invitations, roles, and org-scoped data — eliminates weeks of custom development. Choose Clerk if you need enterprise SSO (SAML, Okta, Azure AD) without building the integration yourself, or if MFA and device session management need to work reliably without custom engineering. Choose Clerk if you're prototyping or launching fast and auth needs to be a solved problem from day one so you can focus on your actual product.

Choose NextAuth v5 if you need full data ownership and cannot have user data stored in a third-party service — this is a real requirement in healthcare, government, and certain financial services contexts. Choose NextAuth if you're building a consumer app where per-MAU pricing would become significant as you scale, or if your project is open source and you want a dependency stack with zero commercial cost. Choose NextAuth if your auth requirements are unusual enough that Clerk's model doesn't fit — custom token structures, specialized credential types, deep integration with an existing user store.

The 2026 reality is that most developers starting a new SaaS project reach for Clerk first, because the developer experience is substantially better and the free tier covers meaningful early growth. NextAuth v5 remains the right choice for open source projects, cost-sensitive consumer apps, and teams that prefer to own their entire infrastructure stack. The decision is rarely irreversible in early development but becomes increasingly painful to change once users have established accounts — so it's worth making deliberately rather than defaulting to whichever tutorial you read first.


Compare authentication APIs and providers at APIScout.

Related: Set Up Clerk Authentication in Next.js 2026, API Authentication Guide: Keys, OAuth & JWT (2026), API Authentication Methods Compared

The API Integration Checklist (Free PDF)

Step-by-step checklist: auth setup, rate limit handling, error codes, SDK evaluation, and pricing comparison for 50+ APIs. Used by 200+ developers.

Join 200+ developers. Unsubscribe in one click.