Skip to main content

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

FeatureAuth.js (NextAuth)ClerkSupabase Auth
PriceFreeFree (up to 10k MAU) → $25+/moFree (up to 50k MAU) → $25+/mo
Self-hosted
Email/Password
Magic Links
OAuth Providers40+20+20+
Passkeys⚠️ Limited
MFA/2FAManual✅ Built-in✅ Built-in
Organizations/TeamsManual✅ Built-inManual
User Management UI✅ Dashboard + embeddable✅ Dashboard
Session managementJWT / DBJWT (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

BoilerplateAuth Solution
ShipFastAuth.js (NextAuth)
SupastarterSupabase Auth
MakerkitSupabase Auth or Clerk
T3 StackAuth.js
Epic StackCustom (Remix session)
Open SaaSWasp 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.

Comments