Introduction
Next.js middleware is one of its most powerful and underused features. It runs at the edge — meaning it executes before your page even starts rendering, with near-zero latency. You can intercept every request, inspect headers and cookies, and redirect, rewrite, or modify the response.
Here are three real-world use cases I use in production.
How middleware works
Create a file called middleware.ts in the root of your project (next to app/). It exports a function that receives a NextRequest and returns a NextResponse.
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
Use case 1: Protect authenticated routes
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("auth-token")?.value;
const isProtected = request.nextUrl.pathname.startsWith("/dashboard");
if (isProtected && !token) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("redirect", request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
This runs at the edge — the user is redirected before the dashboard even starts rendering. No flash of protected content.
Use case 2: A/B testing with cookies
export function middleware(request: NextRequest) {
const bucket = request.cookies.get("ab-bucket")?.value;
const response = NextResponse.next();
if (!bucket) {
const newBucket = Math.random() < 0.5 ? "a" : "b";
response.cookies.set("ab-bucket", newBucket, { maxAge: 60 * 60 * 24 * 30 });
if (newBucket === "b" && request.nextUrl.pathname === "/") {
return NextResponse.rewrite(new URL("/home-v2", request.url));
}
}
if (bucket === "b" && request.nextUrl.pathname === "/") {
return NextResponse.rewrite(new URL("/home-v2", request.url));
}
return response;
}
Users in bucket B see /home-v2 but the URL stays as /. The cookie persists for 30 days so the same user always sees the same variant.
Use case 3: Geo-based redirects
export function middleware(request: NextRequest) {
const country = request.geo?.country || "US";
if (request.nextUrl.pathname === "/" && country === "IN") {
return NextResponse.redirect(new URL("/in", request.url));
}
if (request.nextUrl.pathname === "/" && country === "GB") {
return NextResponse.redirect(new URL("/uk", request.url));
}
return NextResponse.next();
}
request.geo is available on Vercel deployments automatically. No third-party IP geolocation service needed.
Combining all three
You can chain all of these in a single middleware.ts. Just make sure the auth check runs first, since you don't want to A/B test unauthenticated users on protected pages.
Conclusion
Next.js middleware is one of those features that, once you start using it, you wonder how you shipped without it. Auth protection, experimentation, and localisation — all in one file, running at the edge before your server ever touches the request.
