DevStacked
Authentication•Updated •14 min read

Clerk vs Auth.js (NextAuth) vs Better Auth in 2026: Compared

If you're starting a new Next.js project in 2026, picking an auth solution isn't as simple as it used to be. NextAuth has been renamed to Auth.js and only receives security and critical fixes, Better Auth has taken over its maintenance and was acquired by Vercel, and Clerk has kept pushing further into "auth + user management" rather than just sign-in forms.

Most comparison posts still talk about NextAuth v4 and Clerk's old pricing. This guide compares the three options as they actually ship today, with real code, and ends with a decision table you can use for your own project.


Quick Answer

  • Choose Clerk if you want the fastest setup, prebuilt UI, and built-in user management (organizations, session management, admin dashboard), and you're fine with your users living on Clerk's infrastructure. Passkeys and MFA need the paid Pro plan.
  • Choose Better Auth if you're starting a new project and want full data ownership plus modern features (passkeys, 2FA, organizations, RBAC) as plugins, with no per-user pricing.
  • Choose Auth.js v5 if you're maintaining an existing NextAuth codebase, or you only need OAuth + JWT sessions and want the library with the longest production track record.

What's Changed Recently

Auth.js (formerly NextAuth)

  • The package is still installed as next-auth on npm, but the project rebranded to Auth.js and supports frameworks beyond Next.js through @auth/core.
  • v5 is a full rewrite. The old pages/api/auth/[...nextauth].ts + authOptions pattern is gone; everything comes from a single NextAuth() call.
  • Auth.js has been maintained by Better Auth Inc. since September 2025. It continues to get security and critical fixes, but no new features, and its maintainers now recommend Better Auth for most new projects.
// auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [GitHub],
});
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
// proxy.ts
export { auth as proxy } from "@/auth";

No more getServerSession(authOptions): you call auth() anywhere on the server.

⚠️ Common Mistake: Running npm install next-auth without the @beta tag installs the old v4 API. As of October 2026, next-auth@latest still resolves to v4 (4.24.x); v5 is only published under the beta tag, so install it with npm install next-auth@beta.

Better Auth

  • Vercel announced it was acquiring Better Auth on July 7, 2026. The library is MIT-licensed, and the announcement commits to keeping it open source and framework- and platform-agnostic.
  • It's framework-agnostic (Next.js, Nuxt, SvelteKit, TanStack Start, Expo, plain Node) and TypeScript-first, with a small core plus a plugin system for passkeys, 2FA, organizations and RBAC.
// lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/lib/db";

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: "pg" }),
  emailAndPassword: { enabled: true },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
});
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

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

toNextJsHandler() maps every request under /api/auth/* to Better Auth's internal handlers, the same job Auth.js's handlers export does. The catch-all folder name ([...all]) is arbitrary; it just has to match the client's basePath (default /api/auth).

Clerk

Clerk hasn't rewritten its API, but a few things differ from older tutorials:

  • Middleware is now proxy.ts in Next.js 16 (it was middleware.ts in Next.js 15 and earlier: same code, new filename).
  • Since @clerk/nextjs Core 3 (March 2026), <SignedIn>, <SignedOut> and <Protect> are replaced by a single <Show> component: <Show when="signed-in">, <Show when="signed-out">, or <Show when={{ role: "admin" }}>.
  • The free Hobby plan covers 50,000 monthly retained users per app, which is enough for most side projects and early-stage SaaS apps. Passkeys and MFA are on the Pro plan.
// proxy.ts
import { clerkMiddleware } from "@clerk/nextjs/server";

export default clerkMiddleware();

export const config = {
  matcher: [
    "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
    "/(api|trpc)(.*)",
    "/__clerk/(.*)",
  ],
};

By default clerkMiddleware() doesn't protect anything. You opt routes into protection explicitly, which is worth knowing before you assume your dashboard is locked down.


Setup Time

This is still the biggest practical difference.

  • Clerk: install @clerk/nextjs, add your keys to .env, add clerkMiddleware() to proxy.ts, and wrap your layout in <ClerkProvider>. You get a working hosted sign-in/sign-up flow in well under 15 minutes, with no database, schema or email provider setup.
  • Auth.js v5: create auth.ts and the route handler. With JWT sessions and one OAuth provider, that's the fastest self-hosted path. Add a database adapter and your own sign-in UI (Auth.js ships no components) and a first-time setup is realistically an hour or more.
  • Better Auth: with a database, you need one extra step to create the schema, which puts a working integration around 30 minutes. With the built-in Kysely adapter, the CLI applies it for you:
npx auth@latest migrate

With Drizzle or Prisma (like the example above), migrate isn't supported. Generate the schema, then apply it with your ORM's own migration tool:

npx auth@latest generate
npx drizzle-kit migrate # or: npx prisma migrate dev

💡 Tip: Since Better Auth 1.5, the CLI is the standalone auth package (npx auth@latest). The older @better-auth/cli package is deprecated; if a tutorial uses it, it's the same generate/migrate tool under its previous name.

Better Auth can run without a database

Since Better Auth 1.4 (November 21, 2025), you can omit the database option and run stateless, signed-cookie sessions: the same JWT-style trade-off Auth.js defaults to.

// lib/auth.ts: stateless, no database
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
});

⚠️ Common Mistake: Assuming stateless mode unlocks the plugins too. passkey(), organization() and two-factor auth all persist real records, so they still need a database.


Reading the Session

Auth.js: auth() reads cookies internally, with no arguments.

// app/dashboard/page.tsx
import { auth } from "@/auth";

export default async function DashboardPage() {
  const session = await auth();
  return <h1>Welcome back, {session?.user?.name}</h1>;
}

Better Auth: the server-side check is a plain API method that you pass request headers into.

// app/dashboard/page.tsx
import { auth } from "@/lib/auth";
import { headers } from "next/headers";

export default async function DashboardPage() {
  const session = await auth.api.getSession({ headers: await headers() });
  return <h1>Welcome back, {session?.user?.name}</h1>;
}

On the client, Better Auth's createAuthClient() from better-auth/react gives you a typed useSession() hook. Auth.js has the equivalent useSession() from next-auth/react, wrapped in a <SessionProvider>.

Clerk: auth() from @clerk/nextjs/server returns the signed-in userId, and currentUser() returns the full user object. On the client, use hooks like useUser().


Customization & Branding

  • Clerk gives you prebuilt components (<SignIn />, <SignUp />, <UserButton />) that are themeable but still recognizably Clerk unless you build custom flows with its lower-level APIs.
  • Auth.js and Better Auth give you zero UI. You design every form, error state and redirect: more work, but your auth pages match the rest of your app with no "de-Clerking" effort.

Data Ownership & Vendor Lock-In

  • Clerk stores your users on its infrastructure. You get a dashboard, audit logs and organization management, but user data lives outside your database unless you sync it via webhooks.
  • Auth.js and Better Auth store users and sessions wherever you point the adapter: your own Postgres, MySQL, SQLite or MongoDB. You own the schema and the data.

For SaaS products where "we never see our own user data" is a hard no from a client or compliance team, a self-hosted library is the safer default.


Passkeys, 2FA and Organizations

This is where the three options diverge most.

  • Auth.js ships a WebAuthn/Passkeys provider that its own docs call "experimental and not recommended for production use". It also needs a compatible database adapter and an extra Authenticator table. There's no organizations concept and no built-in 2FA: you build them yourself.
  • Better Auth treats them as first-class plugins:
// lib/auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { passkey } from "@better-auth/passkey";

export const auth = betterAuth({
  // ...your database and provider config
  plugins: [passkey(), organization()],
});

The passkey() plugin adds the database fields, registration and sign-in flows, so users can sign in with Face ID, Windows Hello or a hardware key. organization() adds organizations, invitations, member roles and the schema for all of it.

⚠️ Common Mistake: Forgetting to update the schema after adding a plugin (npx auth@latest migrate with Kysely, or generate plus your ORM's migration otherwise). Each plugin adds its own tables, and it fails at runtime, not build time, if the schema hasn't caught up.

  • Clerk includes organizations (teams, invitations, roles) on every plan, enabled from its dashboard; the free tier allows up to 20 members per organization, and unlimited members is a paid add-on. Passkeys and MFA are built in but require the Pro plan. It's the least code of the three, as long as you accept the hosted model.

Security: Don't Rely on proxy.ts Alone

Whichever library you pick: Next.js had a documented middleware bypass (CVE-2025-29927) where a spoofed x-middleware-subrequest header could skip middleware checks entirely. It's patched, but proxy.ts should never be your only line of defense.

// Inside a Server Action or Route Handler

// Auth.js
const session = await auth();
if (!session) return { error: "Unauthorized" };

// Better Auth
const session = await auth.api.getSession({ headers: await headers() });
if (!session) return { error: "Unauthorized" };

// Clerk (auth from "@clerk/nextjs/server")
const { userId } = await auth();
if (!userId) return { error: "Unauthorized" };

Re-check the session inside Server Actions and Route Handlers that touch real data. Treat proxy-level redirects as a UX nicety, not your security boundary.


Pricing

ClerkAuth.js v5Better Auth
LicenseHobby plan free up to 50,000 MRU; Pro from $25/month for passkeys, MFA and moreFree, open source (ISC)Free, open source (MIT)
Hosting costNone for auth itselfYour DB, only with database sessionsYour DB, needed for persistent users and most plugins
Hidden costPaid add-ons at scale (e.g. unlimited org members)Dev time to build 2FA, orgs, admin toolingOwning security and maintenance yourself

The self-hosted libraries are "free" in license terms, but you trade subscription cost for development time: password reset, email verification and admin tooling are paid in hours instead of dollars.


Feature Comparison

FeatureClerkAuth.js v5Better Auth
StatusActively developed (hosted service)Security and critical fixes onlyActively developed, owned by Vercel
Setup timeFastestFast (JWT) / slower with a DBMedium (schema step)
Prebuilt UIYesNoNo
Data ownershipHosted by ClerkFully yoursFully yours
Session modelManaged by ClerkJWT by default, DB optionalDB by default, stateless optional
PasskeysBuilt in (Pro plan)Experimental, not production-readyPlugin (needs a DB)
Two-factor authBuilt in (Pro plan)Build it yourselfPlugin (needs a DB)
Organizations / teamsBuilt in (20 members free)Build it yourselfPlugin (needs a DB)
Session revocationWithin ~60 seconds (short-lived tokens)Immediate only with DB sessionsImmediate with DB sessions (the default)
Framework supportSDKs for Next.js, React, Expo, Astro and moreNext.js-first (+ @auth/core)Framework-agnostic
Maintenance burdenLowMediumMedium

Migrating From Auth.js to Better Auth

If you already run Auth.js, a move is a parallel implementation, not a drop-in swap: the session models and table shapes differ. A safer path:

  1. Add Better Auth alongside Auth.js, on new routes only.
  2. Create Better Auth's schema (npx auth@latest generate plus your ORM's migration), then write a data migration script for existing users. Better Auth doesn't do this part for you.
  3. Move features one at a time (sign-in/sign-up, then protected routes), and remove Auth.js once nothing depends on it.

If Auth.js is working and you don't need a feature it can't give you, there's no urgency to migrate.


Decision Table

Your situationPick
MVP, want auth solved in an afternoonClerk
B2B SaaS needing orgs, and hosted users are fineClerk
B2B SaaS needing orgs, users must stay in your DBBetter Auth
New project needing passkeys or 2FA, self-hostedBetter Auth
Maintaining an existing NextAuth v4/v5 appAuth.js
Only OAuth + basic sessions, no databaseAuth.js (JWT) or Better Auth (stateless)
Building outside Next.js too (Nuxt, SvelteKit, Expo)Better Auth
Want to avoid per-user pricing as you scaleAuth.js or Better Auth

Frequently Asked Questions

Is NextAuth the same thing as Auth.js?

Yes. NextAuth renamed itself Auth.js when it expanded beyond Next.js. The npm package is still next-auth; only the project name changed.

Is Auth.js dead?

No, but it's no longer gaining features. Since September 2025 it receives security and critical fixes only, and existing apps don't need to migrate urgently. New feature work happens in Better Auth.

Do I need a database for Better Auth?

Not for basic email/password or OAuth since v1.4 (stateless mode). You do need one for persistent user records and for most plugins, including passkeys, organizations and 2FA.

Will Vercel's acquisition change Better Auth's license?

Better Auth is MIT-licensed, and the July 2026 announcement says Vercel shares its commitment to keeping auth open source and framework- and platform-agnostic. No license change has been announced.

Is Clerk free?

The Hobby plan is free up to 50,000 monthly retained users per app, including basic organizations. Passkeys and MFA require the Pro plan ($25/month, or $20/month billed annually), and some features such as unlimited organization members are paid add-ons. Check Clerk's pricing page for current numbers.


Final Recommendation

For most freelance and SaaS MVP work in 2026, Clerk is still the fastest way to ship, especially if you'll need organizations later. For a new self-hosted project, Better Auth is the stronger default: it has passkeys, 2FA and organizations as plugins, it's where active development is happening, and Auth.js's own maintainers recommend it for most new projects. Auth.js v5 is the right call when you're maintaining an existing NextAuth codebase or only need simple OAuth sessions.

💡 Worth knowing before you follow the hype: Better Auth has real community momentum, but evaluate it against your actual requirements, not just its popularity on Reddit. Some developers who switched because "everyone recommended it" hit missing features they needed.

None of these choices is permanent: plenty of teams start with Clerk to validate an idea and move to a self-hosted option once they have real scale and real requirements.


Useful Resources

Continue Learning

Free Developer Tools

  • Supabase RLS Policy Generator: pair application-level auth with database-level Row Level Security, including owner-only, team and admin-override policies.
  • Zod Schema Generator: generate a validated schema for your auth or role payloads from a JSON sample or TypeScript interface.
clerknextauthauth.jsbetter-authnext.js 16authenticationsaas
Share On