Crash Courses · Masters
Middleware & Authentication
Protect routes and redirect users with middleware and Next.js auth patterns.
11 min read+175 XP on completionCert: Next.js Crash Course
Tap any word in the text below to start reading from there.
Middleware & Authentication
Middleware runs at the CDN edge before every matching request — before the request reaches your server, before the database is touched. This makes it ideal for:
- Authentication guards (redirect to login if no session)
- Geo-based redirects (different content by country)
- A/B testing (cookie-based feature flags)
- Rate limiting headers
Because middleware runs on the Edge Runtime (a V8 environment, not full Node.js), it has no access to fs, path, or other Node APIs. It runs in milliseconds near the user.
Basic Middleware — Auth Guard
// middleware.ts (root of the project, next to package.json)
import { NextRequest, NextResponse } from 'next/server'
export function middleware(req: NextRequest) {
const session = req.cookies.get('session')?.value
const { pathname } = req.nextUrl
const isProtected = pathname.startsWith('/dashboard') ||
pathname.startsWith('/courses') ||
pathname.startsWith('/profile')
if (isProtected && !session) {
const loginUrl = new URL('/login', req.url)
loginUrl.searchParams.set('callbackUrl', pathname)
return NextResponse.redirect(loginUrl)
}
return NextResponse.next() // proceed to the route normally
}
// Only run middleware on these routes (not static files, not API routes)
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|api/).*)',
],
}The matcher pattern excludes Next.js internals and static assets — if you skip this, middleware runs on every image and CSS file too.
Role-Based Access
export function middleware(req: NextRequest) {
const session = req.cookies.get('session')?.value
const role = req.cookies.get('role')?.value
const { pathname } = req.nextUrl
// Admin-only routes
if (pathname.startsWith('/admin') && role !== 'admin') {
return NextResponse.redirect(new URL('/403', req.url))
}
// Any authenticated route
if (
(pathname.startsWith('/dashboard') || pathname.startsWith('/courses')) &&
!session
) {
const loginUrl = new URL('/login', req.url)
loginUrl.searchParams.set('callbackUrl', pathname)
return NextResponse.redirect(loginUrl)
}
return NextResponse.next()
}Reading Auth in Server Components
For auth checks in server components (not middleware), use the cookies() helper:
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { verifyToken } from '@/lib/auth'
async function getUser() {
const cookieStore = await cookies()
const token = cookieStore.get('auth-token')?.value
if (!token) redirect('/login') // throws internally — stops rendering
const user = await verifyToken(token)
if (!user) redirect('/login')
return user
}
// Protected page — automatically redirects if not authenticated
export default async function DashboardPage() {
const user = await getUser() // redirects to /login if no valid session
return <Dashboard user={user} />
}Adding User Context to Headers
Middleware can attach data to headers, making it available to server components downstream:
export function middleware(req: NextRequest) {
const token = req.cookies.get('auth-token')?.value
if (!token) {
return NextResponse.redirect(new URL('/login', req.url))
}
// Decode token (lightweight — no DB call at edge)
const userId = decodeJwtSub(token) // edge-compatible JWT decode
// Pass user id to downstream server components via headers
const res = NextResponse.next()
res.headers.set('x-user-id', userId)
return res
}Reading progress
0% read
In this module
middleware.ts
Edge Runtime
config.matcher
NextResponse.redirect
0%