Next.js middleware is the worst-documented feature in the framework. The official docs show you a hello-world example, ship it, and discover in production that JWT verification fails, your users hit redirect loops, and half your database library doesn't work.
I've shipped middleware-based auth on three production apps. Here's the setup I use, after all the bugs.
The mental model
Middleware runs on every request before the route handler. It runs on the Edge Runtime by default, which is a restricted sandbox — no Node built-ins like crypto or fs. It's fast (cold start in milliseconds) but limiting.
// middleware.ts (in the root of your project, next to app/)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Runs on every matched request
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*'],
}