Middleware and the Request Lifecycle
The idea in one sentence
Section titled “The idea in one sentence”A middleware.ts file at the root of your project runs BEFORE a request finishes, on every matching route, so you can redirect, rewrite, or set headers and cookies before Next.js renders anything.
Where middleware sits in the request lifecycle
Section titled “Where middleware sits in the request lifecycle”Middleware intercepts the request after it arrives but before the route is resolved. You return a NextResponse to redirect, rewrite, or continue with NextResponse.next().
// middleware.ts — runs BEFORE the request completes, on every matching routeimport { NextResponse } from 'next/server'import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) { // Lightweight auth gate: redirect logged-out users away from /dashboard const isLoggedIn = request.cookies.has('session') if (request.nextUrl.pathname.startsWith('/dashboard') && !isLoggedIn) { return NextResponse.redirect(new URL('/login', request.url)) }
// Otherwise continue, and set a header on the way through const response = NextResponse.next() response.headers.set('x-request-time', Date.now().toString()) return response}Scope it with matcher
Section titled “Scope it with matcher”Without a matcher, middleware runs on every request — including static assets. Scope it so it only runs where you need it. The matcher config accepts path patterns and can exclude internals.
// middleware.ts — scope which routes run middlewareexport const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],}Redirects, rewrites, and the Edge runtime
Section titled “Redirects, rewrites, and the Edge runtime”Use middleware for redirects, rewrites (same URL, different content), and setting headers or cookies. A rewrite serves different content without changing the browser URL — useful for A/B tests or geo routing.
// A/B or geo rewrite — same URL, different content servedimport { NextResponse } from 'next/server'import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) { const country = request.headers.get('x-vercel-ip-country') ?? 'us' return NextResponse.rewrite(new URL(`/${country}${request.nextUrl.pathname}`, request.url))}Middleware runs on the Edge runtime by default. It sits in front of every matching request, so keep it fast: no database calls, no heavy computation, no large dependencies. Do the lightweight decision here and push real work to the route.
graph LR A["Incoming request"] --> B["middleware.ts (Edge)"] B -->|redirect| C["NextResponse.redirect"] B -->|rewrite| D["NextResponse.rewrite"] B -->|next| E["Route handler / page"]