Authentication in Next.js: NextAuth vs Clerk vs Supabase Auth
Authentication is one of the first decisions you make in a new project and one of the hardest to change later. Here's how the three leading options compare in a production Next.js context.
Nextcraft Engineering Team
Why Auth Choice Matters More Than You Think
Authentication touches every layer of your stack: middleware, server components, API routes, client state. Choosing the wrong solution means either ripping it out six months in, or living with architectural debt that makes every new feature harder.
We've shipped production apps with all three of the major Next.js auth solutions. Here's what actually matters.
NextAuth.js (Auth.js v5)
Best for: Custom auth flows, multiple OAuth providers, self-hosted requirements.
NextAuth v5 (now branded Auth.js) was rebuilt for the App Router. The API is cleaner than v4 and integrates naturally with Server Components and middleware.
// auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
callbacks: {
authorized({ auth, request }) {
return !!auth?.user; // Protect routes in middleware
},
},
});
// In a Server Component
import { auth } from '@/auth';
export default async function Dashboard() {
const session = await auth();
if (!session) redirect('/login');
return <DashboardContent user={session.user} />;
}
Strengths:
- Free and self-hosted — no per-MAU costs
- Highly configurable: custom session strategies, JWT handling, database adapters
- 40+ OAuth providers built in
- Strong community, mature codebase
Weaknesses:
- You manage the complexity: email templates, password reset flows, MFA all require custom implementation
- Database setup required for session persistence
- No built-in UI components — you build your own sign-in pages
- Debugging session issues can be painful
Verdict: Best for teams comfortable with backend work who want zero vendor lock-in and full control.
Clerk
Best for: Rapid development, teams that want auth solved completely.
Clerk provides hosted authentication with pre-built UI components, a full user management dashboard, and an SDK that integrates deeply with Next.js App Router patterns.
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);
export default clerkMiddleware((auth, req) => {
if (isProtectedRoute(req)) auth().protect();
});
// In a Server Component
import { currentUser } from '@clerk/nextjs/server';
export default async function Profile() {
const user = await currentUser();
return <div>Hello {user?.firstName}</div>;
}
Strengths:
- Complete solution: sign-up, sign-in, MFA, SSO, user profiles, org management
- Pre-built, customizable UI components that look professional out of the box
- Excellent Next.js middleware integration — route protection in 5 lines
- Works edge-side in middleware without database queries
- Webhooks for user events
Weaknesses:
- Costs money at scale ($0.02/MAU after free tier)
- Less control over session logic
- Your users' auth data lives on Clerk's infrastructure
- Customization beyond their theming system requires workarounds
Verdict: Best for SaaS products where shipping fast matters more than minimizing vendor dependencies. The time saved on auth is significant.
Supabase Auth
Best for: Teams already using Supabase for their database.
Supabase Auth is built on GoTrue and integrates tightly with Supabase's Row Level Security (RLS) system. If you're using Supabase as your database, using anything else for auth means giving up the RLS integration — which is a significant loss.
// Create a Supabase server client
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export function createClient() {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ cookies: { getAll: () => cookieStore.getAll() } }
);
}
// In a Server Component
export default async function Page() {
const supabase = createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) redirect('/login');
// RLS automatically filters data to the current user
const { data: posts } = await supabase.from('posts').select();
return <PostList posts={posts} />;
}
Strengths:
- RLS integration is genuinely powerful — database-level security without custom query filters
- Free tier is generous
- Built-in email/password, OAuth, magic links, phone auth
- Self-hostable with Docker
Weaknesses:
- Cookie management with App Router requires careful setup (the
@supabase/ssrpackage) - Less polished middleware integration than Clerk
- Organization/team management not built in
- Weaker TypeScript DX than the alternatives
Verdict: The obvious choice if Supabase is your database. Otherwise, only worth it if you specifically want self-hosted and free.
The Decision Matrix
| Criteria | NextAuth | Clerk | Supabase Auth |
|---|---|---|---|
| Cost at scale | Free | $0.02/MAU | Free/self-hosted |
| Setup time | Days | Hours | Hours |
| Customization | Maximum | Moderate | Moderate |
| Built-in UI | None | Full | Basic |
| MFA | DIY | Built-in | Built-in |
| Org management | DIY | Built-in | Limited |
| Database integration | Via adapters | Webhooks | Native (Supabase) |
| Self-hosted | Yes | No | Yes |
Our Recommendation
- Bootstrapped SaaS / side project: Clerk — ship faster, worry about it later
- Enterprise / compliance requirements: NextAuth with a custom database adapter
- Using Supabase database: Supabase Auth, no question
- High scale, cost-sensitive: NextAuth or self-hosted Supabase
The wrong choice isn't catastrophic. Migrating auth is painful but not impossible. But getting it right upfront saves you a week of migration work at the worst possible moment — usually right before a launch.
Continue reading
Related articles
Why Next.js App Router Is Better for SEO Than Pages Router
The App Router isn't just a new file-system convention — it fundamentally changes how search engines crawl and index your Next.js application.
EngineeringServer Components vs Client Components: Making the Right Call
The boundary between Server and Client Components is the most consequential architectural decision you make in a Next.js application. Here's how to draw it correctly.
EngineeringBuilding High-Performance Next.js Applications for Scale
A deep dive into how we utilize App Router and React Server Components to scale our client builds effectively.
Stay Informed.
Join 1,200+ founders and engineers receiving our monthly deep dives on product engineering, design, and growth.