# NextAuth v5 with App Router: The Complete Setup Guide
By Govind Bajaj · 2025

Tags: nextauth, next.js, auth, app-router, security
NextAuth v5 rewrites the auth experience for Next.js App Router. One config file. Edge-compatible. Server Components supported. Here's the production setup I use.
I migrated the Sans Herbals auth system from NextAuth v4 to v5 last quarter.

The change was not incremental. v5 is a ground-up rewrite with App Router as the first-class citizen. The same config that took 80 lines in v4 now takes 15. Edge compatibility works out of the box. And the auth() helper in Server Components eliminated an entire category of session-fetching boilerplate.

## What Changed in v5

NextAuth v5 (now called Auth.js) introduces several architectural changes:

- App Router-first design with Pages Router still supported
- OAuth support on preview deployments without extra config
- Simplified setup with shared config and inferred environment variables
- Edge-compatible by default
- The auth() function replaces getServerSession, getSession, withAuth, and useSession in Server Components

## The Config File

```ts
// auth.ts
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import { MongoDBAdapter } from '@auth/mongodb-adapter'
import clientPromise from './lib/mongodb'

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  adapter: MongoDBAdapter(clientPromise),
  session: { strategy: 'jwt' },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.role = user.role
      }
      return token
    },
    async session({ session, token }) {
      session.user.role = token.role as string
      return session
    },
  },
})
```

Notice how compact this is. The providers array lists your auth providers. The adapter connects to your database for session storage. The callbacks let you customize JWT claims and session data.

## The Route Handler

```ts
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers
```

That is it. Two lines. The handlers object from your auth config exports the GET and POST handlers that NextAuth needs.

## Using auth() in Server Components

This is the biggest quality-of-life improvement in v5. Server Components can call auth() directly:

```tsx
// 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>
      <h1>Welcome, {session.user.name}</h1>
      <p>Role: {session.user.role}</p>
    </div>
  )
}
```

No client-side session fetching. No loading states. No useSession hook. The session is available immediately because the Server Component runs on the same server as the auth system.

## Middleware for Route Protection

```ts
// middleware.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'

export default auth((req) => {
  if (!req.auth && req.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', req.url))
  }
})

export const config = {
  matcher: ['/dashboard/:path*', '/api/protected/:path*']
}
```

The auth middleware runs at the edge. Unauthorized requests to protected routes are redirected before any page code executes. This is the correct layer for route protection — not in every page component.

## Client Components Still Use useSession

For interactive components that need session data, the pattern remains familiar:

```tsx
'use client'
import { useSession, signOut } from 'next-auth/react'

export function UserMenu() {
  const { data: session } = useSession()

  if (!session) return null

  return (
    <div>
      <span>{session.user.name}</span>
      <button onClick={() => signOut()}>Sign out</button>
    </div>
  )
}
```

## Takeaways

- NextAuth v5 is App Router-first with a dramatically simpler config
- The auth() function in Server Components eliminates session-fetching boilerplate
- Middleware is the correct layer for route protection, not individual pages
- Edge compatibility works out of the box — no extra configuration needed
- The JWT callback is where you add custom claims like role or teamId
- Migration from v4 is straightforward: most configs shrink by 60-80%