Auth.js vs Better Auth in 2026: Which Should You Use?
You're starting a new Next.js project and you need auth. A year ago the answer was simple: "just use NextAuth." That's not simple anymore. NextAuth is now called Auth.js, it's sitting in maintenance mode, and the team that used to build it has folded into a different project entirely — Better Auth, which Vercel just bought.
If you Google "Auth.js vs Better Auth" today, half the results are outdated and the other half assume you already know the backstory. You don't need the backstory to make a good decision — you just need the current facts, side by side, with real code. That's what this guide gives you.
By the end, you'll know exactly what each library does well, what each one is missing, and which one fits your project — whether that's a solo side project, a client SaaS app, or something you're migrating from an old NextAuth v4 codebase.
Why This Comparison Is Confusing Right Now
Three things changed in a short window, and most tutorials online haven't caught up:
- NextAuth renamed itself Auth.js when it expanded beyond Next.js to support other frameworks. The npm package is still called
next-auth— only the project's public name changed. - Auth.js is now maintained by Better Auth Inc., not its original team. Since early 2026, Auth.js has been in maintenance mode: security patches only, no new features.
- Vercel acquired Better Auth on July 7, 2026. Better Auth stays free, MIT-licensed, and open source — Vercel says it isn't closing that off — but it's now the auth library with Vercel's own resources and roadmap behind it, not a scrappy independent project.
Put simply: if you pick Auth.js today, you're picking a stable, feature-frozen library. If you pick Better Auth, you're picking the actively developed one that absorbed Auth.js's own team.
That doesn't automatically make Better Auth the right answer for every project — "actively developed" and "right for you" aren't the same thing. Let's get into specifics.
Quick Answer
- Choose Auth.js (NextAuth) if you're maintaining an existing NextAuth v4/v5 codebase, you only need OAuth + JWT sessions with zero extra features, or you want the library with the longest production track record.
- Choose Better Auth if you're starting a new project today and want built-in passkeys, 2FA, organizations, or RBAC without hand-rolling them, or you want a library that's still receiving new features.
For a brand-new Next.js project in 2026 with no existing NextAuth investment, Better Auth is the stronger default. Now here's why.
AUTH.JS
│
┌─────────┴─────────┐
│ │
JWT sessions DB sessions
│ │
no DB required DB required
BETTER AUTH
│
┌────────────┴────────────┐
│ │
Stateless mode Database mode
│ │
no DB required DB-backed sessions
│
┌────────────┼────────────┐
│ │ │
Passkeys 2FA Organizations
│ │
DB-backed DB-backed
features features
What Each Library Actually Is
Auth.js (formerly NextAuth)
Auth.js is a session and OAuth layer built specifically around Next.js's App Router conventions. You configure a single auth.ts file, wire it into a catch-all route handler, and call auth() anywhere on the server to read the current session.
npm install next-auth@beta
⚠️ Common Mistake: Running
npm install next-authwithout the@betatag installs the old v4 API (authOptions,getServerSession(), a completely different setup). As of mid-2026,next-auth@lateststill resolves to v4 — v5 is only reachable through thebetatag, even though it's what almost every current tutorial (including this one) assumes.
// 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;
That's a complete, working GitHub OAuth setup — no database required if you're fine with JWT sessions. auth() is now the single entry point for reading the session anywhere on the server, replacing the old v4 pattern of getServerSession(authOptions).
Better Auth
Better Auth is framework-agnostic (Next.js, Nuxt, SvelteKit, TanStack Start, plain Node) and TypeScript-first. Instead of one library that does OAuth and sessions, it ships a small core plus a plugin system — passkeys, 2FA, organizations, and RBAC are opt-in plugins, not separate libraries you bolt on yourself.
npm install better-auth
// 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);
What's happening here: toNextJsHandler() is Better Auth's official helper for wiring its core into a Next.js App Router catch-all route — it maps every request under /api/auth/* to the right internal handler, the same way Auth.js's handlers export does.
💡 Tip: The folder is
app/api/auth/[...all]/route.ts, not[...nextauth]. Better Auth's catch-all segment name is arbitrary — it just needs to match whateverbasePathyou configure on the client (the default is/api/auth).
Better Auth Can Also Run Without a Database Now
For a long time, "no database, no Better Auth" was the biggest practical reason to reach for Auth.js instead. That gap closed with Better Auth 1.4 (November 2025): you can now omit the database option entirely and run fully stateless, signed-cookie sessions — the same JWT-style model Auth.js has always defaulted to.
// lib/auth.ts — stateless, no database at all
import { betterAuth } from "better-auth";
export const auth = betterAuth({
// No `database` option — session data lives in a signed/encrypted cookie
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
});
What's happening here: with no database configured, Better Auth verifies session cookies by signature and expiry instead of looking them up in a table — functionally the same trade-off as Auth.js's JWT sessions (fast, no DB round trip, but no server-side "delete this one session" revocation).
⚠️ Common Mistake: Assuming stateless mode unlocks the full plugin ecosystem too. Better Auth's own docs are explicit that most plugins still require a database —
passkey(),organization(), and two-factor auth all persist real records (credentials, memberships, invitations) that a signed cookie can't hold. Stateless mode covers basic email/password and OAuth sign-in; reach for a database the moment you add a plugin.
So the database gap between the two libraries has narrowed for basic auth, but it's still real the moment you want any of Better Auth's headline plugin features.
Setup Time
Auth.js, with JWT sessions and one OAuth provider, is genuinely the fastest path to a working sign-in flow — install, create auth.ts, add the route handler, done. No database, no schema migration.
Better Auth, with a database adapter (Drizzle, Prisma, Kysely, or a handful of others), needs one extra step — generating and applying the schema — before it will run:
npx auth@latest generate
npx auth@latest migrate
💡 Tip: These commands used to live under a separate
@better-auth/clipackage. As of 2026 the CLI is published as theauthpackage on npm and invoked withnpx auth@latest <command>— if you seenpx @better-auth/cli generatein an older tutorial, it's the same tool under its previous name.
That's a few extra minutes of setup compared to Auth.js's JWT mode — but it's also the reason a database-backed Better Auth setup gets you a real user table, instant session revocation, and its plugin ecosystem for free once it's running. If you don't want the database at all, Better Auth's stateless mode closes most of that setup-time gap for basic email/password and OAuth sign-in — the schema migration step above is only needed once you configure a database or add a plugin that requires one.
💡 Tip: Even without your own feature needs pointing you one way or the other, this is still a reasonable rule of thumb: default to Auth.js (or Better Auth's stateless mode) for a genuinely tiny side project, and to a database-backed Better Auth setup for anything that will eventually need passkeys, 2FA, organizations, or persistent user records.
Reading the Session
Auth.js — Server Component
// 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 — Server Component
// 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>;
}
What's different here: Auth.js's auth() reads cookies internally with no extra arguments. Better Auth's server-side session check is a plain API method (auth.api.getSession) that you explicitly pass request headers into — a small but real difference in ergonomics, since you have to remember the headers() call every time.
Better Auth — Client Component
// components/user-menu.tsx
"use client";
import { authClient } from "@/lib/auth-client";
export function UserMenu() {
const { data: session, isPending } = authClient.useSession();
if (isPending) return null;
if (!session) return <a href="/sign-in">Sign in</a>;
return <span>Welcome, {session.user.name}</span>;
}
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL,
});
What's happening here: createAuthClient() from better-auth/react gives you a typed useSession() hook that stays in sync with the server — if the session expires or the user signs out in another tab, it updates automatically. Auth.js has the equivalent useSession() hook from next-auth/react, wrapped in a <SessionProvider> at the root of your app.
Feature Comparison
| Feature | Auth.js (v5) | Better Auth |
|---|---|---|
| Status | Maintenance mode — security patches only | Actively developed, now backed by Vercel |
| Requires a database | No (JWT sessions work standalone) | No for basic auth since v1.4 (stateless mode) — still required for most plugins (passkeys, 2FA, organizations) |
| Session model | JWT by default, database sessions optional | Database sessions by default, stateless signed-cookie sessions optional since v1.4 |
| OAuth providers | Large built-in provider ecosystem | Built in, growing list |
| Passkeys (WebAuthn) | Not built in — experimental Passkey providerexists but needs an adapter & is marked not production-ready | Built-in passkey() plugin, ~10 lines of config |
| Two-factor auth | Build it yourself | Built-in plugin |
| Organizations / multi-tenancy | Build it yourself | Built-in plugin |
| Role-based access control | Build it yourself | Built-in plugin |
| Framework support | Next.js-first (also SvelteKit, Express via @auth/core) | Framework-agnostic — Next.js, Nuxt, SvelteKit, TanStack Start, Expo, plain Node |
| Instant session revocation | Only with database sessions | Yes, by default |
| Edge runtime friendly | Yes, with JWT sessions | Depends on adapter/runtime support |
Passkeys: The Feature Gap That Matters Most
If passkey sign-in is a requirement, this is where the two libraries genuinely diverge, not just in convenience.
Auth.js ships an experimental Passkey provider, but the docs are explicit that it's not recommended for production, and it requires switching from stateless JWT sessions to a full database adapter just to use it — a real architectural change, not a one-line addition.
Better Auth treats passkeys as a first-class plugin:
npm install @better-auth/passkey
// lib/auth.ts
import { betterAuth } from "better-auth";
import { passkey } from "@better-auth/passkey";
export const auth = betterAuth({
// ...your database and provider config
plugins: [passkey()],
});
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
import { passkeyClient } from "@better-auth/passkey/client";
export const authClient = createAuthClient({
plugins: [passkeyClient()],
});
What's happening here: the passkey() plugin (powered by SimpleWebAuthn internally) adds the database fields, registration flow, and authentication flow for you. After running the CLI migration, users can register and sign in with Face ID, Windows Hello, or a hardware key with no custom WebAuthn code on your end.
⚠️ Common Mistake: Forgetting to run
npx auth@latest migrate(orgenerate+ your ORM's own migration step) after adding a new plugin likepasskey()ororganization(). Each plugin adds its own tables/columns, and — since passkeys need somewhere to persist credentials — this is also exactly the case where Better Auth's stateless, no-database mode stops being an option. The plugin will fail at runtime, not build time, if the schema hasn't caught up.
Organizations and Multi-Tenant Apps
If you're building B2B SaaS — teams, workspaces, invitations, per-organization roles — this is the other place the gap is large.
Auth.js has no organizations concept at all. You'd model teams yourself with your own database tables and write your own invitation, membership, and permission logic on top of Auth.js's session.
Better Auth has this as a plugin:
// lib/auth.ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
// ...your database and provider config
plugins: [organization()],
});
This one plugin gives you organization creation, invitations, member roles, and the database schema for all of it.
Security: Don't Rely on Middleware Alone, With Either Library
This applies regardless of which one you pick. Next.js had a documented middleware bypass (CVE-2025-29927) where a spoofed header could skip middleware checks entirely — including auth checks. It's patched now, but it's a good reminder that proxy.ts (the renamed middleware.ts in Next.js 16) should never be your only line of defense.
// Inside a Server Action, regardless of which library you use
const session = await auth(); // Auth.js
// or
const session = await auth.api.getSession({ headers: await headers() }); // Better Auth
if (!session) {
return { error: "Unauthorized" };
}
Always re-check the session inside Server Actions and Route Handlers that touch real data. Treat proxy.ts redirects as a UX nicety — a way to bounce an obviously-signed-out user before a page even renders — not your actual security boundary.
💡 Tip: If you're not familiar with how sessions and JWTs differ under the hood — including why database sessions support instant revocation and JWTs don't — see Session vs JWT: Which Should You Use in Next.js 16? for the full breakdown.
Pricing
Both libraries are free and open source under MIT. Neither charges per user, per seat, or per monthly active user — that's the core pitch of self-hosted auth versus a managed service like Clerk.
The real cost difference is development time, not license fees:
| Auth.js | Better Auth | |
|---|---|---|
| License cost | $0 | $0 |
| Hosting cost | Only if using database sessions | Only if you add a database (needed for persistent users or most plugins) |
| Dev time for passkeys / 2FA / orgs | You build it | Plugin, minutes of config — requires a database |
| Dev time for basic OAuth + sessions | Minutes | Minutes, stateless mode needs no DB setup at all |
If you need advanced features, Auth.js's "free" license comes with a real time cost — you're the one building 2FA or organizations from scratch. Better Auth trades a slightly longer initial setup for a lot less custom code later.
Migrating From Auth.js to Better Auth
If you already have a working Auth.js app and are considering a move, know this upfront: it's a parallel implementation, not a drop-in swap. The session models are different enough (JWT vs. database sessions by default, different table shapes) that a big-bang cutover is riskier than it needs to be.
A safer path:
- Add Better Auth alongside your existing Auth.js setup, on new routes only.
- Migrate your user table to Better Auth's schema using its CLI (
npx auth@latest generate, then a manual data migration script for existing users — Better Auth doesn't do this part for you). - Move features one at a time — start with sign-in/sign-up, then migrate protected routes, then remove Auth.js once nothing depends on it.
- If you need zero-downtime migration, design an explicit transition strategy so existing Auth.js sessions can continue working while Better Auth is introduced.
💡 Tip: Migration can require manual schema and identity mapping, so test the migration path before production cutover
If you have no existing NextAuth investment, skip this section entirely — just start with whichever library fits your feature needs from Step 1 above.
Decision Table
| Your situation | Pick |
|---|---|
| Maintaining an existing NextAuth v4/v5 app | Auth.js |
| New project, only need OAuth + basic sessions, no database yet | Auth.js (JWT mode) or Better Auth (stateless mode) — either works |
| New project, want passkeys or 2FA without building them yourself | Better Auth (needs a database for these plugins) |
| B2B SaaS needing organizations, teams, or RBAC | Better Auth |
| Need Edge-runtime session checks with zero database calls | Auth.js (JWT mode) |
| Want instant, guaranteed session revocation by default | Better Auth |
| Building outside Next.js too (Nuxt, SvelteKit, Expo) | Better Auth |
| Want the library actively gaining features month over month | Better Auth |
Frequently Asked Questions
Is Auth.js dead?
No — it's in maintenance mode, not abandoned. It still receives security patches, and existing apps using it don't need to migrate urgently. What's stopped is new feature development; that work moved to Better Auth after the Better Auth team took over Auth.js's maintenance.
Is NextAuth the same thing as Auth.js?
Yes. NextAuth renamed itself to Auth.js when it expanded beyond Next.js to other frameworks. The npm package is still installed as next-auth for backwards compatibility — only the public project name changed.
Does Better Auth work with the Next.js App Router?
Yes, and it's first-class supported. Better Auth ships a toNextJsHandler() helper specifically for wiring its core into an App Router catch-all route, and its React client works in both Server and Client Components with the patterns shown above.
Do I need a database for Better Auth?
Not necessarily anymore. As of Better Auth 1.4 (November 2025), you can omit the database option entirely and run stateless, signed-cookie sessions — the same JWT-style trade-off as Auth.js. You'll still need a database adapter (Drizzle, Prisma, Kysely, and others are supported) the moment you want persistent user records or most plugins, since the docs are explicit that plugins like passkey(), organization(), and two-factor auth require one.
Has the Better Auth CLI command changed?
Yes. It used to be published under @better-auth/cli; it's now the auth package on npm, invoked as npx auth@latest generate and npx auth@latest migrate (plus newer commands like info and create-admin). If you see @better-auth/cli in an older tutorial, it's the same tool under its previous package name — the underlying generate/migrate behavior hasn't changed.
Will Vercel's acquisition of Better Auth change the pricing or license?
As of the acquisition, Vercel confirmed Better Auth stays free and MIT-licensed, keeps its name, and the original team continues leading development with the same open contribution model. There's no indication of a license change.
Can I use Better Auth outside of Next.js?
Yes — that's one of its core differences from Auth.js. Better Auth is framework-agnostic and has documented support for Nuxt, SvelteKit, TanStack Start, Express, Hono, and even Expo for React Native, all sharing the same core auth logic.
Wrapping Up
Auth.js and Better Auth solve the same core problem — proving who a user is and keeping that state in sync across your app — but they're at very different points in their lifecycle right now. Auth.js is stable, well-documented, and frozen in scope. Better Auth is younger, can now run database-free for basic auth just like Auth.js, and is where the ecosystem's energy (and now Vercel's resources) are actually going — the database is only a hard requirement once you reach for its plugin ecosystem.
For a brand-new Next.js project in 2026 with no legacy NextAuth code to maintain, Better Auth is the safer long-term bet, especially if passkeys, 2FA, or organizations are anywhere on your roadmap. If you're maintaining something that already runs on Auth.js and it's not causing problems, there's no urgency to rip it out — keep shipping, and revisit the decision only when you hit a feature Auth.js genuinely can't give you.
From here, a natural next step is pairing whichever library you pick with Role-Based Access Control on top of the session, or reading Session vs JWT in Next.js 16 to decide which session model actually fits your app before you commit to one.
Continue Learning
- How to Add NextAuth (Auth.js v5) in Next.js 16
- Add Clerk Authentication to Next.js 16 (2026)
- Clerk vs NextAuth (Auth.js) in 2026: Which One Should You Choose?
- Supabase Auth vs Firebase Auth: Which One Should You Choose in 2026?
Free Developer Tools
- Supabase RLS Policy Generator — pair this guide's application-level RBAC with database-level Row Level Security. Generates owner-only, team, and admin-override policies with the
auth.uid()performance wrapper built in. - Zod Schema Generator — generate a validated schema for your role/permission payloads straight from a JSON sample or TypeScript interface, including a ready-to-use Server Action.
- Vercel Cron Expression Generator - generate Vercel-compatible cron expressions with a visual schedule builder, live validation, & a preview of the next 5 execution times in UTC.
More in Authentication
View AllSession vs JWT: Which Should You Use in Next.js 16?
Session vs JWT in Next.js 16 compared: how each works, real code with jose, Server Actions, and proxy.ts, and how to pick the right one for your app.
Supabase Auth vs Firebase Auth: Which One Should You Choose in 2026?
Supabase Auth vs Firebase Auth compared for 2026 — setup, pricing, Next.js App Router integration, security, and which one fits your project.
Role-Based Access Control (RBAC) in Next.js 16 (2026 Beginner's Guide)
Learn how to implement role-based authorization (RBAC) in Next.js 16 App Router with TypeScript, Auth.js v5, Proxy, Server Actions, Route Handlers, and permissions.
How to Add NextAuth (Auth.js v5) in Next.js 16
Add NextAuth (Auth.js v5) to Next.js 16 step by step — providers, sessions, protected routes, proxy.ts, and experimental passkey (WebAuthn) sign-in.