better-auth vs Lucia vs NextAuth: The Auth Library Landscape in 2026
TL;DR
Authentication in JavaScript has been in flux. NextAuth (now Auth.js) still dominates downloads, but better-auth is the fastest-growing newcomer with a genuinely better TypeScript DX, and Lucia filled a gap before sunsetting its abstraction layer. If you're starting a new TypeScript project today, better-auth is the most compelling choice — it's the first auth library that actually feels designed for TypeScript from the ground up.
Key Takeaways
- better-auth: 50K → 500K weekly downloads in 12 months — the fastest-growing auth library
- NextAuth v5 / Auth.js: ~1.5M weekly downloads — still dominant but rebranded and restructured
- Lucia v3: Pivoted from full auth library to auth architecture guide — no longer recommended as a dependency
- better-auth wins on TypeScript ergonomics, plugin system, and two-factor support
- NextAuth wins on ecosystem size, documentation depth, and framework coverage
- If you're using Next.js and want minimal setup: NextAuth. For maximum control and type safety: better-auth.
Download Trends
| Package | Weekly Downloads | 6-Month Growth | First Release |
|---|---|---|---|
next-auth | ~1.5M | +8% | 2020 |
@auth/core | ~400K | +45% | 2023 |
better-auth | ~500K | +820% | 2024 |
lucia | ~150K | -40% | 2022 |
The Full Story
NextAuth v5 / Auth.js: The Incumbent
NextAuth is the default choice for Next.js authentication. With ~1.5M weekly downloads and 7 years of ecosystem maturity, it's battle-tested across thousands of production applications.
The v5 rewrite (released as @auth/core) brought significant changes:
- Framework-agnostic core (
@auth/core) with adapters for Next.js, SvelteKit, Express, Astro, and Qwik - Edge runtime support
- New universal
auth()helper that works in both server and client contexts - Callbacks API remains but with cleaner TypeScript types
What's still frustrating about NextAuth:
// Config file can get unwieldy with all options
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [GitHub],
callbacks: {
session({ session, token }) {
// TypeScript doesn't automatically know what's in token
// You must augment types manually
session.user.id = token.sub as string
return session
}
}
})
Type augmentation is required for any custom session properties — a friction point that better-auth eliminates.
better-auth: The TypeScript-First Challenger
better-auth takes the position that existing auth libraries weren't designed with TypeScript in mind, and it shows. Every part of the API is fully typed from configuration to session reading.
import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { twoFactor, magicLink, passkey } from "better-auth/plugins"
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "postgresql"
}),
plugins: [
twoFactor(), // TOTP + backup codes
magicLink(), // Email magic links
passkey() // WebAuthn/passkeys
],
emailAndPassword: {
enabled: true,
requireEmailVerification: true
}
})
// Client-side — fully typed, no config needed
import { createAuthClient } from "better-auth/react"
const { signIn, signUp, useSession } = createAuthClient()
// Session type is automatically inferred — no manual augmentation
const { data: session } = useSession()
console.log(session.user.id) // ✅ Fully typed
Why developers are switching:
- Plugin system: Two-factor auth, magic links, passkeys, OAuth, organization management — all as typed plugins
- No manual type augmentation: Session types are automatically inferred from your config
- Organization/multi-tenant support: Built-in team/organization features that NextAuth requires custom code for
- Edge-native: Designed for Cloudflare Workers, Vercel Edge, and Bun from day one
- Active development: ~3 releases per week in 2025-2026
Lucia: The Architecture Guide
Lucia was originally a lightweight auth library that gave developers full control without magic. In late 2024, its maintainer Pilcrow made a pivotal decision: rather than maintain a shrinking library as better-auth gained traction, he sunset Lucia as a dependency and turned it into an auth architecture guide.
This was a selfless move — he acknowledged that better-auth had surpassed Lucia and redirected developers accordingly.
Key quote from the Lucia readme:
"Lucia is no longer maintained. If you were using it, consider using better-auth instead."
For new projects, don't install Lucia. Read its documentation to understand auth patterns, then implement with better-auth or NextAuth.
Feature Comparison
| Feature | better-auth | NextAuth v5 | Lucia (archived) |
|---|---|---|---|
| TypeScript DX | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Session management | DB + JWT | JWT-first | DB sessions |
| OAuth providers | 40+ | 80+ | Manual |
| Two-factor auth | Plugin (built-in) | Community | Manual |
| Magic links | Plugin (built-in) | Community | Manual |
| Passkeys/WebAuthn | Plugin (built-in) | No | Manual |
| Organization/teams | Plugin (built-in) | No | No |
| Email+password | Built-in | Built-in | Manual |
| Database adapters | Drizzle, Prisma, Mongoose, Kysely | 15+ | Custom |
| Framework support | Any (Node, Edge, Bun) | Next.js-first + others | Any |
| Bundle size | ~45KB | ~25KB core | ~12KB |
| Weekly downloads | ~500K | ~1.5M | ~150K |
Session Strategies
NextAuth v5: JWT by default
// JWT session (default — no database needed):
export const { handlers, auth } = NextAuth({
providers: [GitHub],
// No adapter = JWT sessions
})
// Database sessions (with adapter):
export const { handlers, auth } = NextAuth({
adapter: DrizzleAdapter(db),
session: { strategy: "database" },
providers: [GitHub],
})
better-auth: Database sessions by default
// better-auth always uses database sessions
// (no JWT session tokens floating around)
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "postgresql" }),
providers: [oauth({ ... })]
})
The choice between JWT and database sessions is a real architectural decision:
- JWT: Stateless, works without a database, but can't be revoked without a blocklist
- Database sessions: Instantly revocable, slightly more latency, requires DB query per request
Migration: NextAuth → better-auth
// NextAuth config (before):
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [
GitHub({ clientId: env.GITHUB_ID, clientSecret: env.GITHUB_SECRET }),
Credentials({
credentials: { email: {}, password: {} },
authorize: async (credentials) => {
// ... validate
}
})
],
callbacks: {
session: ({ session, token }) => {
session.user.role = token.role
return session
}
}
})
// better-auth config (after):
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "postgresql" }),
socialProviders: {
github: {
clientId: env.GITHUB_ID,
clientSecret: env.GITHUB_SECRET
}
},
emailAndPassword: { enabled: true },
plugins: [
admin() // Role-based access built-in
]
})
When to Use Each
Choose NextAuth v5 / Auth.js if:
- You're already using it in production — migration costs are real
- You need the widest possible OAuth provider coverage (80+ providers)
- Your team is more familiar with the NextAuth mental model
- You need framework adapters for SvelteKit, Astro, or Qwik specifically
- You want the most community resources and Stack Overflow answers
Choose better-auth if:
- Starting a new TypeScript project in 2026
- You want two-factor auth, passkeys, or magic links without community plugins
- You need organization/multi-tenant support
- Your TypeScript types shouldn't require manual augmentation
- You're building on Cloudflare Workers, Bun, or an edge runtime
Don't use Lucia (as a library) if:
- Starting a new project — read the docs, then use better-auth
- You want maintained dependencies
Methodology
Data sourced from npm registry, GitHub star history, and community surveys. Download counts represent weekly average (Feb 2026). Bundle sizes measured with bundlephobia for the core package only.