Next.js Middleware is a single middleware.ts file at your project root that runs before a request reaches a route — page, Route Handler, or otherwise:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("session")?.value;
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
"Runs at the edge" means this code executes in a lightweight runtime deployed geographically close to the person making the request, before that request ever reaches the servers actually running the bulk of your app — deliberately, so redirect and rewrite decisions happen with as little added latency as possible, on the smallest, fastest execution environment available.
Common real uses fall into a handful of recurring shapes:
request.geo (or an equivalent header depending on your deploy target) to redirect visitors to a region-specific page or apply region-specific rules.Middleware's speed comes at a real cost in capability, and it's worth being precise about the two constraints that matter most:
A mistake worth naming directly: reaching for middleware to do a database-backed permission check ("does this specific user own this specific resource") instead of a coarse, cookie-or-token-based check ("is there a valid session at all"). That fine-grained check belongs in the actual route or Server Action, which runs in a full server environment — middleware is for the cheap, coarse gate in front of it, not a replacement for real authorization logic deeper in the app.