Middleware
Run code before a request reaches a page. Auth, redirects, rewrites and A/B tests live here.
Middleware is a single function that runs on every matching request, before any page or route handler. It runs on the Edge runtime by default — fast, but with a restricted Node API.
The file
Create middleware.js at the root of the project (next to app/, not inside it).
// middleware.js
import { NextResponse } from "next/server";
export function middleware(request) {
const { pathname } = request.nextUrl;
if (pathname.startsWith("/admin")) {
const isLoggedIn = Boolean(request.cookies.get("session"));
if (!isLoggedIn) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/admin/:path*", "/account/:path*"],
};What middleware can return
- **
NextResponse.next()** — continue to the page. - **
NextResponse.redirect(url)** — send the user elsewhere. - **
NextResponse.rewrite(url)** — keep the URL but render a different route. - **
new Response(body, init)** — short-circuit with your own response (e.g., 401).
Matchers
The matcher config narrows down which paths trigger middleware. Use it religiously — running middleware on every static asset is wasteful.
export const config = {
matcher: [
"/dashboard/:path*",
"/api/:path*",
{
source: "/((?!_next/static|_next/image|favicon.ico).*)",
missing: [{ type: "header", key: "x-skip-mw" }],
},
],
};Setting headers and cookies on responses
const res = NextResponse.next();
res.cookies.set("session-touched", Date.now().toString());
res.headers.set("x-country", request.geo?.country ?? "unknown");
return res;Try it
Write middleware that redirects any unauthenticated request to `/account/*` to `/login`. Users are authenticated when there is a `token` cookie.
Need a hint?
Read the cookie with request.cookies.get() and redirect when it's missing.
Quiz
Pick the best answer. You only get one shot per question.
1. Where must the middleware file live?
2. Which return value lets the request continue to its original destination?
3. Why use a `matcher` config?