AuthApr 20264 min read

Lucia vs NextAuth.js — When to Ditch the Kitchen Sink

Lucia gives you auth without the bloat. NextAuth.js gives you everything — including headaches you didn't ask for.

🧊Nice Pick

Lucia

Lucia doesn't force you into a framework-shaped box. You get clean, type-safe primitives without the dependency hell that NextAuth.js brings to non-Next.js projects.

Two Philosophies on Authentication

NextAuth.js started as the de facto auth solution for Next.js — it's the kitchen sink approach where you get OAuth, email/password, magic links, and session management all bundled together. Lucia takes the library approach: it gives you the building blocks (sessions, users, adapters) and lets you assemble your own auth flow. NextAuth.js assumes you're building a Next.js app; Lucia assumes you're building something that needs authentication, period.

This isn't just about features — it's about architectural mindset. NextAuth.js wants to own your auth layer with its predefined routes and callbacks. Lucia wants to be a dependency in your auth layer. If you've ever fought with NextAuth.js's pages/api/auth/[...nextauth].ts structure in a non-standard setup, you know exactly why this matters.

Where Lucia Wins

Lucia's killer feature is framework agnosticism. It works with Next.js, SvelteKit, Astro, Express, or even vanilla Node.js without any special configuration. Its API is type-safe by design with first-class TypeScript support — you're not guessing what's in the session object. The adapter system is clean: plug in Prisma, Drizzle, or MongoDB directly without wrestling with wrapper layers.

Pricing? Lucia is free and open-source (MIT license) with no tiers or paid plans. You're not paying for features you don't need, and there's no vendor lock-in. Compare that to NextAuth.js's Vercel-centric ecosystem where advanced features like advanced security or custom providers might push you toward paid solutions or workarounds.

Where NextAuth.js Holds Its Own

If you're deep in the Next.js ecosystem, NextAuth.js is still the path of least resistance. Its built-in OAuth providers (Google, GitHub, etc.) are literally one-line configurations. The admin dashboard for managing sessions and users, while basic, exists out-of-the-box. For rapid prototyping where you need social logins in under an hour, NextAuth.js delivers.

It's also free for core features, though you'll hit limitations like rate-limiting on email providers or needing to self-host for production-scale OAuth. But let's be clear: this advantage only holds if your entire stack is Next.js. Stray outside that, and the 'convenience' turns into configuration hell.

The Gotcha: Switching Costs

Migrating from NextAuth.js to Lucia isn't trivial. You're not just swapping libraries — you're rewriting your auth flow from callbacks and providers to explicit handlers. Session management, token rotation, and database schemas all change. NextAuth.js's lock-in isn't technical, it's conceptual: once you've structured your app around its patterns, extracting it feels like surgery.

Lucia to NextAuth.js is easier technically but philosophically jarring — you're trading control for convenience. The real cost isn't in lines of code; it's in future flexibility. Choose NextAuth.js today, and you're committing to Next.js's roadmap. Choose Lucia, and you're committing to your own.

If You're Starting Today...

Use Lucia if you value long-term maintainability over short-term convenience. Start with its lucia package, pick an adapter for your database, and build exactly what you need. You'll write more code initially, but you'll own every line. This is especially true for multi-framework projects or when you're not sure if you'll stick with Next.js in two years.

Use NextAuth.js if you're building a Next.js app with standard social logins and you'll never, ever change frameworks. Accept that you're trading control for speed, and be okay with that. Just don't come crying when you need to add a custom authentication method and find yourself patching around NextAuth.js's abstractions.

What Most Comparisons Get Wrong

Most reviews treat this as a feature checklist battle — 'NextAuth.js has more providers, so it wins.' That's missing the point. The real question is: do you want auth as a product or auth as a library? NextAuth.js is a product (even though it's free) with opinions and boundaries. Lucia is a library with primitives and escape hatches.

They also ignore the TypeScript experience. NextAuth.js's types are an afterthought — you'll be casting any more than you'd like. Lucia's are generated from your schema, so your session user is exactly what you defined. In 2024, that's not a nice-to-have; it's a requirement.

Quick Comparison

FactorLuciaNextAuth.js
Framework SupportAny (Next.js, SvelteKit, Astro, etc.)Next.js-first, others via workarounds
TypeScript SupportFirst-class, schema-generated typesBasic, often requires manual typing
Built-in OAuth ProvidersNone (implement yourself)50+ (Google, GitHub, etc.)
Database AdaptersPrisma, Drizzle, MongoDB, etc.Prisma, TypeORM, etc. (Next.js-focused)
PricingFree (MIT license)Free core, paid for advanced features
Session ManagementExplicit, customizableAutomatic, opinionated
Learning CurveSteeper (build your own flow)Shallow (for Next.js users)
Production ScalabilityHigh (you control everything)Medium (depends on Vercel/Next.js)

The Verdict

Use Lucia if: You're building a multi-framework app, need type-safe auth, or plan to scale beyond Next.js.

Use NextAuth.js if: You're prototyping a Next.js app with social logins and want it done yesterday.

Consider: Clerk if you need a full-featured, hosted auth service with UI components and can stomach the $25/month starter plan.

🧊
The Bottom Line
Lucia wins

Lucia doesn't force you into a framework-shaped box. You get clean, type-safe primitives without the dependency hell that NextAuth.js brings to non-Next.js projects.

Related Comparisons

Disagree? nice@nicepick.dev