better-auth vs Lucia vs NextAuth 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
Ecosystem & Community
NextAuth has the largest community of any JavaScript auth library. With 7+ years of production use, there are thousands of Stack Overflow answers, blog posts, and YouTube tutorials. The Auth.js Discord server is active, and the framework adapter ecosystem (SvelteKit, Astro, Express) ensures NextAuth works in non-Next.js contexts. The 15+ official database adapters (Prisma, Drizzle, TypeORM, Mongoose, PrismaAdapter) mean NextAuth integrates with any ORM out of the box.
better-auth (18k+ GitHub stars by early 2026) has grown remarkably fast for a library that launched in 2024. The creator, Bereket Engida, releases updates multiple times per week and is highly responsive to community feedback. The better-auth Discord community is active, and the documentation — while newer — is comprehensive and well-organized. The plugin ecosystem is expanding rapidly: the official plugins cover two-factor auth, magic links, passkeys, organization management, admin features, and OAuth.
Lucia's legacy is its documentation. Even though the library itself is archived, the Lucia auth guide at lucia-auth.com remains one of the best explanations of how session-based authentication should work — covering cookies, CSRF protection, session management, and database schema design. New developers learning auth from scratch will benefit from reading it before choosing a library.
The auth library landscape in 2026 has consolidated around better-auth and NextAuth for new projects, with a long tail of teams maintaining existing Lucia, Passport.js, and custom implementations.
Real-World Adoption
NextAuth is installed in millions of Next.js projects, ranging from personal portfolios to large production applications. Major companies that ship public Next.js applications — news sites, e-commerce platforms, developer tools — predominantly use NextAuth because it was the obvious choice when they started and migration costs are real. The Vercel team recommends NextAuth as the default auth solution for Next.js in their documentation and starter templates.
better-auth adoption is concentrated in new projects started in 2024-2026. The TypeScript ergonomics and plugin system resonate most strongly with teams building complex applications that need two-factor auth, organization management, or passkey support from the start. Projects that use Drizzle ORM particularly benefit — the Drizzle adapter for better-auth is well-maintained and the type inference between Drizzle schemas and auth types is seamless.
The pattern of "use NextAuth for simple OAuth apps, use better-auth when you need more" has emerged in the developer community. Teams building simple GitHub OAuth login for a developer tool stick with NextAuth. Teams building B2B SaaS with email verification, two-factor auth, and team management increasingly choose better-auth. If you want a head start, several SaaS starter kits with authentication pre-built — including MakerKit and SupaStarter — default to better-auth for exactly these reasons.
Developer Experience Deep Dive
better-auth's TypeScript ergonomics deserve detailed examination because they're the primary reason developers switch. In NextAuth, adding a custom field to the session (say, user.role) requires three things: augmenting the TypeScript session module declaration, updating the session callback to populate the field, and maintaining the augmentation whenever the type changes. It works, but it's friction.
In better-auth, the session type is automatically derived from your configuration and plugins. Add the admin() plugin and session.user.role is typed correctly everywhere without any manual type augmentation. This pattern extends to every plugin — adding twoFactor() to the config makes session.user.twoFactorEnabled available with the correct type throughout your application.
The better-auth client-side API is similarly well-designed. createAuthClient() returns fully typed functions derived from your server configuration — the client knows exactly what methods are available based on which plugins are installed. This end-to-end type inference from server config to client calls is something no other auth library achieves.
NextAuth v5's developer experience improvements over v4 are meaningful. The unified auth() function that works in both App Router and Pages Router contexts, edge runtime support, and the framework-agnostic @auth/core package make it a significantly better library than the v4 era. The type augmentation requirement remains the primary friction point.
Security Architecture
Both better-auth and NextAuth implement authentication security correctly. They handle CSRF protection, secure cookie configuration, PKCE for OAuth flows, and proper token storage. The meaningful security differences are in architecture.
better-auth's default of database sessions provides instant revocability — when a user logs out or is disabled, their session is gone immediately. JWT sessions (NextAuth's default) can't be revoked without a blocklist, which means a compromised JWT is valid until it expires. For applications where immediate session revocation is important (security-sensitive apps, ability to log out all devices), better-auth's database session default is the correct choice.
NextAuth's JWT default is appropriate for applications where statelessness matters — serverless functions that can't share database connections, or edge deployments where database latency would be unacceptable. The security trade-off is explicitly documented in NextAuth's security guide.
Both libraries support MFA — better-auth as a built-in plugin, NextAuth via community adapters. better-auth's WebAuthn/passkey support is a meaningful security upgrade for applications where phishing resistance matters.
Performance Considerations
better-auth's database session approach adds a database query to every authenticated request. For most applications, this is a 1-5ms overhead that's completely acceptable. For high-traffic applications making thousands of requests per second, the database connection pool becomes a bottleneck. better-auth supports Redis-based session caching (via the sessionStore option) to mitigate this.
NextAuth's JWT sessions avoid the per-request database query at the cost of revocability. For serverless applications that initialize a new function instance per request, JWT's statelessness is architecturally cleaner. The token validation is a cryptographic operation (~0.1ms) rather than a database query.
Both libraries add minimal overhead to Next.js applications in practice. The session checks are fast, and the middleware patterns for route protection are efficient. Performance benchmarks between the two show differences of 2-10ms per request in database session scenarios — meaningful at scale, negligible for most applications.
Migration Guide
Migrating from NextAuth to better-auth in an existing application requires careful planning. The data migration is the main challenge: user records, account records (OAuth connections), and session records have different schemas between the two libraries. better-auth provides migration utilities for common ORMs, but custom data transformations may be required.
The API migration is more straightforward. Replace useSession() with better-auth's equivalent, update signIn()/signOut() calls, and replace the auth configuration file. In a typical Next.js application, this migration touches 10-20 files and takes 1-2 days.
Migrating from Lucia to better-auth follows a similar pattern. Lucia's database schema is minimal (users, sessions), and better-auth's default schema is compatible enough that most migrations are simple column renaming operations.
Teams that have migrated report that the TypeScript improvements alone are worth the effort. Eliminating manual type augmentation and getting proper type inference for custom session fields reduces a category of TypeScript errors that previously required careful attention to maintain.
Final Verdict 2026
better-auth is the recommended choice for new TypeScript projects in 2026. The TypeScript-first design, built-in plugin system for two-factor auth and passkeys, organization support, and active development trajectory make it the technically superior choice for new applications.
NextAuth remains the right choice for existing projects and teams with NextAuth expertise. The migration cost is real, and NextAuth v5 is a significantly improved library. There's no urgency to migrate if NextAuth is working well in production.
Don't start new projects with Lucia — follow the maintainer's recommendation and use better-auth instead. Read the Lucia docs to understand auth concepts, then implement with better-auth.
The auth library space will continue to evolve, but better-auth's growth trajectory and development pace suggest it will be the dominant new-project recommendation through at least 2027.
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.
Compare next-auth vs better-auth in real-time on PkgPulse →
Rate Limiting and Brute Force Protection
Authentication endpoints are high-value targets for brute force attacks. Neither better-auth nor NextAuth includes built-in rate limiting — this is intentional, as rate limiting is better handled at the infrastructure or middleware level where you have more control over strategy and storage.
For better-auth, the recommended pattern is middleware-level rate limiting using tools like @upstash/ratelimit for edge deployments or custom Redis-based rate limiting for traditional Node.js servers. Better-auth's plugin system makes it possible to build a rate limiting plugin that hooks into the auth request pipeline, incrementing counters before the authentication attempt and blocking requests that exceed the threshold.
NextAuth v5's middleware-based architecture in Next.js integrates well with Vercel's Edge Middleware, where rate limiting via Upstash or Vercel KV is a common pattern. Protecting the /api/auth/callback and /api/auth/signin routes with rate limiting is a standard security hardening step for production NextAuth deployments.
Both libraries support the CAPTCHA integration patterns (hCaptcha, Turnstile, reCAPTCHA) at the form level — the rate limiting is library-agnostic.
Multi-Factor Authentication Deep Dive
Multi-factor authentication support is one of the most concrete differences between the libraries. better-auth ships with a twoFactor() plugin that implements TOTP (Time-based One-Time Passwords) via authenticator apps like Authy and 1Password, plus backup codes for account recovery. The implementation is complete and production-ready: QR code generation for authenticator app setup, TOTP verification on every login, encrypted backup code storage, and recovery flows.
NextAuth v5 does not ship built-in 2FA. Implementing TOTP with NextAuth requires custom credentials provider logic, manual secret generation and storage, and a custom sign-in UI. Community packages like next-auth-2fa provide scaffolding, but they're not official and maintenance varies.
For applications where two-factor authentication is a security requirement rather than a nice-to-have — fintech, healthcare, enterprise SaaS — better-auth's built-in 2FA support eliminates significant custom development. The passkey support is an additional security upgrade: passkeys (WebAuthn) are phishing-resistant by design and represent the authentication future that major platforms are converging on.
Testing Authentication in Your Application
Testing auth flows is notoriously friction-heavy. The session and cookie management that makes auth secure also makes it annoying to test. Both libraries have testing patterns that reduce this friction.
For better-auth, the createAuthClient() function can be instantiated with a custom fetchImplementation that intercepts requests in tests. This lets you simulate signed-in and signed-out states without actual HTTP requests. The typed session structure makes test fixtures straightforward — define a session object matching the inferred type and pass it to components under test.
NextAuth provides an official testing guide using Vitest and @auth/testing. The unstable_getServerSession utility can be mocked to return any session shape in Jest or Vitest tests. For end-to-end tests with Playwright or Cypress, both libraries' signIn() functions can be called with test credentials to set up authenticated state before running navigation tests. For a full guide to testing patterns, including auth testing, see best JavaScript testing frameworks 2026.
Database Schema Design
Understanding the database schema each library requires is important before committing to one. better-auth's default schema includes user, session, account, and verification tables. The account table stores OAuth provider connections (supporting multiple providers per user). The session table stores all active sessions with expiry timestamps and device information. Running better-auth generate produces the exact migration SQL or ORM schema file for your database.
NextAuth's schema is similar when using database sessions: User, Account, Session, and VerificationToken tables. The Drizzle and Prisma adapters define the schema in the respective ORM's format. One practical difference: better-auth's schema is more extensive when plugins are added — adding the organization() plugin adds organization, member, and invitation tables. This is expected and managed through the CLI migration tool.
For teams choosing between library-managed auth and a separate auth service like Clerk or Auth0, see best Next.js auth solutions 2026 for a comparison that includes hosted auth services alongside self-hosted libraries.
Related: Best Next.js Auth Solutions 2026 for a broader look at the Next.js auth ecosystem, Zitadel vs Casdoor vs Authentik 2026 for self-hosted IAM that pairs with these libraries, and Best JavaScript Testing Frameworks 2026 for testing authentication flows in your applications.
If you want authentication handled for you out of the box, see the best SaaS starter kits with authentication pre-built — comparing which boilerplates include Better Auth, Lucia, Auth.js, or Clerk pre-wired.