Skip to main content

Best Next.js Authentication Solutions in 2026

·PkgPulse Team

TL;DR

Auth.js (NextAuth v5) for self-hosted OAuth; Clerk for full-featured managed auth; Better Auth for type-safe self-hosted. Auth.js (~2.5M weekly downloads) is the standard Next.js auth library — handles OAuth in minutes but requires database setup for sessions. Clerk (~300K) is managed auth as a service with beautiful pre-built UI — $0 for 10K monthly users. Better Auth (~100K, fast-growing) is a newer TypeScript-first alternative to NextAuth with better type safety and plugin system.

Key Takeaways

  • Auth.js v5: ~2.5M weekly downloads — App Router, Server Actions, edge compatible
  • Clerk: ~300K downloads — managed, pre-built UI, $0 for 10K users/month
  • Better Auth: ~100K downloads — type-safe, plugin architecture, newer alternative
  • Lucia v3 — session management library (not full auth, you build on top)
  • NextAuth v4 → v5 — major API change; v5 is Auth.js, works across frameworks

Auth.js v5 (Next.js Standard)

// Auth.js v5 — setup
// auth.ts (project root)
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
import { DrizzleAdapter } from '@auth/drizzle-adapter';
import { db } from '@/db';
import { verifyPassword } from '@/lib/crypto';

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: DrizzleAdapter(db),  // Persist sessions to DB
  providers: [
    GitHub({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    }),
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    Credentials({
      credentials: {
        email: { type: 'email' },
        password: { type: 'password' },
      },
      authorize: async ({ email, password }) => {
        const user = await db.query.users.findFirst({
          where: eq(users.email, email as string),
        });
        if (!user) return null;
        const valid = await verifyPassword(password as string, user.passwordHash);
        return valid ? user : null;
      },
    }),
  ],
  callbacks: {
    session({ session, user }) {
      session.user.id = user.id;
      session.user.role = user.role;
      return session;
    },
  },
  pages: {
    signIn: '/login',
    error: '/auth/error',
  },
});
// Auth.js v5 — App Router: route handlers
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
// Auth.js v5 — middleware (protect routes)
// middleware.ts
import { auth } from '@/auth';
import { NextResponse } from 'next/server';

export default auth((req) => {
  const isLoggedIn = !!req.auth;
  const isAuthPage = req.nextUrl.pathname.startsWith('/login');
  const isProtected = req.nextUrl.pathname.startsWith('/dashboard');

  if (isProtected && !isLoggedIn) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  if (isAuthPage && isLoggedIn) {
    return NextResponse.redirect(new URL('/dashboard', req.url));
  }
});

export const config = {
  matcher: ['/dashboard/:path*', '/login'],
};
// Auth.js v5 — Server Component auth check
// app/dashboard/page.tsx
import { auth } from '@/auth';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const session = await auth();
  if (!session) redirect('/login');

  return (
    <div>
      <p>Welcome, {session.user?.name}</p>
      <p>Role: {session.user?.role}</p>
    </div>
  );
}

Clerk (Managed Auth)

// Clerk — setup in Next.js App Router
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher(['/', '/sign-in(.*)', '/sign-up(.*)']);

export default clerkMiddleware((auth, req) => {
  if (!isPublicRoute(req)) auth().protect();
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};
// Clerk — wrap app
// app/layout.tsx
import { ClerkProvider } from '@clerk/nextjs';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html>
        <body>{children}</body>
      </html>
    </ClerkProvider>
  );
}
// Clerk — pre-built UI components (zero custom UI needed)
import { SignIn, SignUp, UserButton, SignedIn, SignedOut } from '@clerk/nextjs';

// Ready-made sign-in page
export default function SignInPage() {
  return <SignIn />;
}

// Conditional rendering
export function Header() {
  return (
    <header>
      <SignedIn>
        <UserButton afterSignOutUrl="/" />
      </SignedIn>
      <SignedOut>
        <a href="/sign-in">Sign In</a>
      </SignedOut>
    </header>
  );
}
// Clerk — server-side user access
import { auth, currentUser } from '@clerk/nextjs/server';

export default async function ProfilePage() {
  const { userId } = auth();
  if (!userId) redirect('/sign-in');

  const user = await currentUser();

  return (
    <div>
      <p>{user?.firstName} {user?.lastName}</p>
      <p>{user?.emailAddresses[0]?.emailAddress}</p>
    </div>
  );
}

Better Auth

// Better Auth — type-safe auth setup
import { betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { db } from '@/db';
import { nextCookies } from 'better-auth/next-js';

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!,
    },
  },
  plugins: [nextCookies()],
});

export type Session = typeof auth.$Infer.Session;
export type User = typeof auth.$Infer.Session.user;

When to Choose

ScenarioPick
New Next.js app, want zero auth workClerk
Budget-conscious, under 10K usersClerk (free)
Self-hosted, data stays yoursAuth.js v5
Better type safety than NextAuthBetter Auth
Social OAuth only (GitHub, Google)Auth.js v5 (simplest)
Organization/team managementClerk
Custom auth flows, complex requirementsAuth.js v5 or Better Auth
Edge runtime requiredAuth.js v5 (edge-compatible)

Compare auth solution package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.