Skip to content

Web Development · Security

Next.js Auth in 2026: Clerk vs Better Auth vs Auth.js v5

Three real authentication options for Next.js apps, with different trade-offs on control, cost, and setup time. Here's what each one actually involves.

Anurag Verma

Anurag Verma

7 min read

Next.js Auth in 2026: Clerk vs Better Auth vs Auth.js v5

Sponsored

Share

Authentication should be a solved problem by now. It isn’t. Every Next.js project still requires a decision: which library, which provider, how much control do you want over the session handling and the UI.

In 2026, there are three options that dominate new projects: Clerk, Better Auth, and Auth.js v5. They solve the same problem differently, and choosing the wrong one for your context costs time later.

The Three Options, Briefly

Clerk is a managed authentication service. You pay for it above a free tier, get pre-built UI components, and hand off the auth state to Clerk’s systems. It does the most for you.

Better Auth is an open-source library that runs in your app. Authentication state lives in your database. You write less code than managing auth yourself but keep full control of the data.

Auth.js v5 (formerly NextAuth.js) is the most established open-source option — battle-tested, 40+ provider adapters, and now fully compatible with the Next.js App Router after a significant rewrite.

Clerk

Clerk’s pitch is time-to-working-auth measured in minutes. Install the package, add the provider, wrap your routes with middleware, and the pre-built <SignIn /> and <SignUp /> components handle the rest.

// app/layout.tsx
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
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";

const isPublicRoute = createRouteMatcher(["/", "/sign-in(.*)", "/sign-up(.*)"]);

export default clerkMiddleware(async (auth, request) => {
  if (!isPublicRoute(request)) {
    await auth.protect();
  }
});

Reading the current user in a Server Component:

import { currentUser } from "@clerk/nextjs/server";

export default async function DashboardPage() {
  const user = await currentUser();
  return <div>Hello {user?.firstName}</div>;
}

That’s it. No database schema to write, no token handling, no session table. Clerk stores the user data and returns it via their SDK.

Where Clerk fits: apps that need organization/workspace support (each user belongs to one or more organizations with different roles), apps that need multi-factor authentication out of the box, or any project where getting auth done fast outweighs controlling every detail. The organization primitive in particular saves weeks of work if your app has a team/company layer.

What Clerk costs: free for up to 10,000 monthly active users. After that, $0.02 per MAU. At 50,000 MAUs, that’s $800/month before any add-ons. For consumer apps that scale, this gets expensive. For B2B SaaS with low user counts, it’s often fine.

What Clerk gives up: user data lives in Clerk’s systems, not yours. Exports are possible but not instant. If Clerk has an outage, your auth is down. If they change pricing, you’re migration-constrained.

Better Auth

Better Auth launched in 2024 and positioned itself as the TypeScript-native alternative to Auth.js. It’s newer, so the ecosystem is smaller, but the API is cleaner and the documentation is better organized.

Setup requires creating an auth instance with your database and desired plugins:

// lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
import { schema } from "@/db/schema";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    schema,
  }),
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
  socialProviders: {
    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!,
    },
  },
});

Add the route handler:

// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { GET, POST } = toNextJsHandler(auth.handler);

Better Auth generates the database schema you need. Run better-auth generate to produce the migration:

npx @better-auth/cli generate

Reading the session in a Server Component:

import { auth } from "@/lib/auth";
import { headers } from "next/headers";

export default async function Dashboard() {
  const session = await auth.api.getSession({
    headers: await headers(),
  });

  if (!session) redirect("/sign-in");
  return <div>Hello {session.user.name}</div>;
}

Where Better Auth fits: apps that want full data ownership, TypeScript-first codebases using Drizzle or Prisma, projects where the team wants to understand and control the auth implementation. The plugin system is solid — two-factor auth, admin features, rate limiting, and team/organization support are all available as plugins rather than a core feature.

What Better Auth costs: free and open source. You’re paying for the database storage and compute, which you’d have anyway.

What Better Auth gives up: newer library means fewer StackOverflow answers, smaller community for edge cases, and occasional rough edges in documentation for the more advanced features.

Auth.js v5

Auth.js (formerly NextAuth.js) is the incumbent. It has 40+ OAuth provider configurations maintained by the community, years of production deployment, and is the answer most StackOverflow questions will lead you to.

v5 rewrote the internals for the App Router. The callback and session handling now works properly with Server Components and the new Next.js data patterns.

// auth.ts (project root)
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),
  providers: [
    GitHub,
    Google,
  ],
  callbacks: {
    session({ session, user }) {
      session.user.id = user.id;
      return session;
    },
  },
});

Route handler:

// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;

Server Component:

import { auth } from "@/auth";
import { redirect } from "next/navigation";

export default async function Dashboard() {
  const session = await auth();
  if (!session) redirect("/api/auth/signin");
  return <div>Hello {session.user?.name}</div>;
}

Where Auth.js v5 fits: apps that need a specific OAuth provider that isn’t in Better Auth’s ecosystem yet, projects that want the most-tested option, and teams where auth.js questions have known answers.

What Auth.js gives up: the API has accumulated quirks over years of backward compatibility. The email/password flow is more manual than the other options. For complex session handling, the callbacks can get convoluted.

Head-to-Head on the Factors That Matter

FactorClerkBetter AuthAuth.js v5
Setup time15 minutes2–4 hours2–3 hours
Email/passwordBuilt-in UIBuilt-inManual implementation
Social providers20+15+40+
Org/team supportFirst-classPluginManual
Data ownershipNo (Clerk’s servers)YesYes
Open sourceNoYesYes
Free tier10k MAUUnlimitedUnlimited
Scale cost$0.02/MAUDatabase costsDatabase costs
TypeScript qualityGoodExcellentGood
Community sizeLargeSmall but growingVery large

Which to Pick

Pick Clerk if: you’re building a B2B SaaS where organizations matter, you want to ship in a day and figure out migration later, or you need features like passkeys and device management without writing them yourself.

Pick Better Auth if: you want open source with full data ownership, you’re already using Drizzle or Prisma and want the auth tables in your own schema, and you’re comfortable with a newer library.

Pick Auth.js v5 if: you need a specific OAuth provider (Apple, Twitch, Spotify), you want the most community resources available, or you’re maintaining a project that already uses it.

For most new B2B SaaS apps, Clerk is the fastest path to an auth system you don’t have to think about. For apps expecting significant scale on a budget, or anywhere data control matters (healthcare, finance), Better Auth is worth the extra setup time. Auth.js remains the choice for projects where breadth of provider support or community size is the constraint.

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored