EmpireUI
Get Pro
← Blog9 min read#authentication#next.js#nextauth

Authentication in Next.js 2026: NextAuth v5, Clerk, Better Auth

NextAuth v5, Clerk, and Better Auth are the three auth options worth your time in 2026. Here's how each one works in the App Router — and which to actually pick.

padlock on glowing circuit board representing Next.js authentication security

The Auth Landscape in 2026 (It's Changed a Lot)

If you set up Next.js auth two or three years ago and haven't looked at it since, you're probably in for a surprise. The ecosystem has shifted dramatically. NextAuth — now officially Auth.js — shipped v5 and rewrote the whole config model. Clerk doubled its market share. And Better Auth showed up from basically nowhere in 2024 and already has more GitHub stars than most established players.

Worth noting: the Pages Router mental model for auth is essentially dead. Everything in 2026 is built around the App Router, Server Components, and middleware. If you've got a project still using getServerSideProps to check sessions, you're carrying real technical debt and this guide will help you understand what the modern approach looks like.

Honestly, the 'right' answer depends heavily on your situation. Are you self-hosting? Do you need social login in the next two hours or do you have a week to wire up a proper backend? Is your team okay paying $25/month or do you want zero vendor lock-in? Those questions matter more than any benchmark. We'll cover all three options so you can make an informed call.

One more thing — we're using Next.js 15.x throughout this guide with the App Router. If you're on an older version some of the middleware patterns will be slightly different, especially around the auth() helper exports.

NextAuth v5 (Auth.js): The Open-Source Stalwart

NextAuth v5 is a complete rewrite. The v4 config that lived in pages/api/auth/[...nextauth].ts is gone. In its place you get a single auth.ts file at your project root that exports everything you need. First-time setup takes about 20 minutes if you've done auth before. Maybe 45 if you haven't.

npm install next-auth@beta
```

Then create `auth.ts`:

```ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [GitHub],
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user
      const isProtected = nextUrl.pathname.startsWith('/dashboard')
      if (isProtected) return isLoggedIn
      return true
    },
  },
})
```

In your `middleware.ts`:

```ts
export { auth as middleware } from './auth'

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}

The auth() function works as a Server Component helper too. Drop it into any RSC and you get the session without a network round-trip — that's the big win over v4. You're reading from the JWT directly in the server context, which keeps latency at near-zero.

That said, v5 still has rough edges as of mid-2026. The database adapter story improved but edge cases with Prisma and Drizzle adapters can burn a couple of hours. The OAuth token refresh flow also requires you to write your own callback logic in a way that feels more verbose than it needs to be. The docs have caught up to the code finally, which was the biggest complaint from the beta period.

In practice, NextAuth v5 is the best choice when you want full control over your data and you're comfortable writing a bit more boilerplate. You own the sessions, you own the tokens, you own the database rows. No monthly bill. No vendor telling you they're changing pricing next quarter.

Clerk: The 'Just Works' Option

Clerk is the auth solution that makes you feel slightly guilty for how easy it is. You get prebuilt UI components, an admin dashboard, MFA, passkeys, organization management, and webhooks — all before you've written a single line of auth logic. The 2026 free tier covers 10,000 monthly active users, which is generous enough for most indie projects.

npm install @clerk/nextjs
```

Wrap your root layout:

```tsx
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}
```

Middleware protection:

```ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'

const isProtected = createRouteMatcher(['/dashboard(.*)', '/account(.*)'])

export default clerkMiddleware((auth, req) => {
  if (isProtected(req)) auth().protect()
})

Reading the session in a Server Component in 2026 is a one-liner: const { userId } = await auth(). That's it. No database call, no cookie parsing. Clerk's edge-first architecture handles all of that in middleware before your component ever runs. For most teams shipping fast, this is a massive productivity win.

Where Clerk gets uncomfortable is scale and lock-in. The prebuilt <SignIn /> component renders Clerk's hosted UI in an iframe by default. It's customizable, but you're always within their design constraints — 8px border-radius minimum on their form elements, specific font loading, etc. You can override a lot of it, but if you have a very specific design (say, a cyberpunk interface like the ones at Empire UI's cyberpunk components), you'll hit limits quickly.

Look, Clerk at $0 is a no-brainer for prototypes. Clerk at $0.02 per MAU once you exceed the free tier is where you need a spreadsheet. Do the math before you're committed.

Better Auth: The Newcomer That Earns Its Name

Better Auth appeared in late 2024 and by 2026 it's become a real contender. The pitch is simple: type-safe, framework-agnostic auth that sits closer to your stack than NextAuth v5 but costs you nothing like Clerk does. It has a plugin architecture that's genuinely clever — you opt into features like 2FA, magic links, and passkeys instead of getting a 500kb bundle whether you want them or not.

npm install better-auth
```

Core setup in `lib/auth.ts`:

```ts
import { betterAuth } from 'better-auth'
import { prismaAdapter } from 'better-auth/adapters/prisma'
import { prisma } from './prisma'

export const auth = betterAuth({
  database: prismaAdapter(prisma, { provider: 'postgresql' }),
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
})
```

Route handler in `app/api/auth/[...all]/route.ts`:

```ts
import { auth } from '@/lib/auth'
import { toNextJsHandler } from 'better-auth/next-js'

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

The TypeScript experience in Better Auth is noticeably better than NextAuth v5. Session types are inferred automatically from your config including any plugins you've added. You won't be casting session.user as CustomUser in 2026 if you pick this one. The plugin types compose correctly — add a twoFactor() plugin and your session type gains twoFactorAuthenticated: boolean automatically.

Quick aside: Better Auth's React hooks (useSession, useSignIn) are suspense-compatible out of the box. If you're using React Server Components patterns heavily, the client-side auth hooks integrate cleanly without triggering unnecessary client boundary bloat.

The downside is community size. Stack Overflow answers are still sparse. When you hit an edge case — and you will — you're in the Discord or reading source code. For teams with junior developers or tight timelines that's a real cost. That said, the source is readable TypeScript, which is more than you can say for some of the older auth libraries.

Middleware Auth Patterns That Actually Work

The biggest gotcha in Next.js App Router auth isn't which library you pick — it's where you check the session. Middleware runs on every request at the edge. Server Components run per-render on the server. They're both 'server' but they're not the same thing, and confusing them leads to subtle bugs.

The correct pattern is: block at middleware, verify in components. Middleware handles the redirect. The Server Component double-checks the session server-side so even if somehow a user bypasses middleware (edge cache mismatch, direct API call), they can't access data they shouldn't see.

// middleware.ts — blocks the route
export default async function middleware(req: NextRequest) {
  const token = req.cookies.get('session-token')?.value
  if (!token && req.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', req.url))
  }
}

// app/dashboard/page.tsx — server-side verification
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const session = await auth()
  if (!session?.user) redirect('/login')
  
  return <div>Welcome {session.user.name}</div>
}

One pattern worth adopting in 2026 regardless of which library you pick: keep your auth() calls in Server Components and pass only the data you need as props to Client Components. Don't pass the whole session object down — pass { userId: string, name: string } or whatever the component needs. It keeps your client bundle lean and your session data from leaking into client-rendered HTML unnecessarily.

For apps with complex role-based access, check out the nextjs-middleware-guide — there's a deep breakdown of how to chain matchers and handle public/private/role-specific routes without writing spaghetti conditions in a single middleware file.

Choosing Between the Three: A Practical Framework

Stop trying to find the objectively best option. There isn't one. Here's how to pick based on your actual situation in under five minutes.

You want NextAuth v5 if: you're self-hosting everything, your team has experience with sessions and JWTs, you need full control over user data for compliance reasons (GDPR, HIPAA, etc.), or you're building something where vendor dependency is a real risk. Budget: $0. Timeline: add a day.

You want Clerk if: you're a solo developer or small team, you want OAuth + MFA + magic links in a morning, your user count is under 10k MAU, and you don't need deeply custom auth UI. The prebuilt components are polished. Your UI elsewhere can still be custom — nobody says your glassmorphism components have to match Clerk's modal styling exactly. Budget: $0–$25/mo for most projects. Timeline: two hours.

You want Better Auth if: you want NextAuth-level control but with a better TypeScript DX, you're building a plugin-heavy feature set (passkeys, 2FA, magic links all together), or you're simply tired of the Auth.js config model. Budget: $0. Timeline: half a day. The tradeoff is community support — you're an early adopter.

In practice, most teams building SaaS in 2026 start with Clerk to ship fast, then migrate to NextAuth v5 or Better Auth once they hit the free tier ceiling or need compliance features. Migration is painful but doable — both libraries share similar session concepts even if the APIs differ. Plan for it eventually and write your auth logic behind a thin wrapper from day one so the swap is a few hundred lines, not a few thousand.

Security Basics You Can't Skip Regardless of Library

The library handles the cryptographic heavy lifting, but there's a layer of security hygiene that's entirely on you. These aren't optional and none of the libraries enforce them automatically.

Set your session cookie with httpOnly, secure, and sameSite: 'lax' at minimum. In 2026 all three major auth libraries default to this, but check your config explicitly if you're customizing the cookie settings at all. One wrong flag and you've opened a session hijacking window.

// Example: explicit cookie hardening in NextAuth v5
export const { handlers, auth } = NextAuth({
  cookies: {
    sessionToken: {
      options: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'lax',
        maxAge: 60 * 60 * 24 * 30, // 30 days
      },
    },
  },
})

Rate limit your auth endpoints. This is the most commonly skipped step. /api/auth/signin, /api/auth/callback/*, password reset routes — all of these should be behind a rate limiter. Next.js middleware is a natural place to add this. Upstash Redis with their rate limiting SDK is the path of least resistance if you're on Vercel. 10 attempts per minute per IP is a reasonable starting point.

Last thing: rotate your AUTH_SECRET environment variable at least once a year. Write it in your calendar now. It's a 30-second change that invalidates all active sessions — yes, your users get logged out, but it's a clean wipe if a secret ever leaks. All three libraries use an env var for this. Keep it long (32+ characters), keep it random, and keep it out of your git history. That means no .env files committed ever, even accidentally. If you want to see a full auth implementation alongside a polished UI, the glassmorphism login page example shows how a real login form integrates with session management in the App Router.

FAQ

Is NextAuth v5 stable enough to use in production in 2026?

Yes. The 'beta' tag is a versioning holdover — Auth.js v5 has been running in production deployments since early 2025. The API is stable; just read the v5-specific docs since most Stack Overflow answers still reference v4 patterns.

Can Clerk components be styled to match a custom design system?

Partially. Clerk's appearance prop lets you override colors, fonts, and border radius. You can match most design systems, but deeply custom layouts like animated or glassmorphism-style forms require switching to Clerk's headless mode with your own UI.

Does Better Auth work with Edge Runtime in Next.js?

Most of it does, but the database adapters (Prisma, Drizzle) don't run on the edge. Use Better Auth with Node.js runtime for routes that touch the database, and the session verification at the edge via JWT works fine.

Which auth library is easiest to migrate away from if needed?

Better Auth, because its session model is plain database rows with standard columns and its JWT format is well-documented. Clerk is the hardest to leave since users exist in Clerk's system — they provide a migration export tool, but it's a real project.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Next.js App Router in 2026: What's Changed and What Still Trips People UpNext.js Server Actions in 2026: Forms, Mutations and the Right PatternsClerk vs NextAuth v5 in 2026: Which Auth Should You Use?Better Auth Guide 2026: Sessions, Social, 2FA in Next.js