Next.js middleware is the gate that runs after a request hits your app and before a route decides what HTML or JSON to send. That is why teams keep looking here for redirects, auth cookies, and locale — the fix lives one layer earlier than the page.
If you only put logic inside a Server Component or a useEffect, the browser already started loading the wrong URL. Middleware exists so you can change the path, set a header, or bounce unauthenticated traffic before that work begins.
What Next.js Middleware Is For
Think of it as a short checklist at the door, not the whole office behind the desk.
- Redirects: old marketing URLs, trailing slashes,
wwwvs apex, or a/loginbounce when a session cookie is missing. - Headers: security headers, a request ID, or a flag your layout reads later.
- Light gates: “this path needs a cookie” or “this path is US-only,” not a full database lookup on every hit.
It is a poor place for slow I/O, large libraries, or business rules that belong in a Server Action or an API route. Keep the file small — this code runs on every matched request.
On Next.js 15 and earlier, that file is middleware.ts at the project root (or under src/). On Next.js 16+, the same job moved to the proxy.ts convention, and the exported function is named proxy — same idea, new name. The rest of this guide uses the word middleware for the pattern; rename the file and export if your major version expects proxy.ts.

A Minimal Next.js Middleware Example
A common first win is a redirect that does not wait for React.
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname === "/old-pricing") {
return NextResponse.redirect(new URL("/website-pricing", request.url));
}
const response = NextResponse.next();
response.headers.set("x-pathname", pathname);
return response;
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
The matcher matters. Without it, middleware can run on every static asset and burn budget for no reason. Scope it to the paths you actually care about — dashboard prefixes, /app/*, or a short list of legacy URLs.
For auth-shaped gates (cookie present → continue, missing → /login), use the same pattern, then harden the session check on the server. Our Next.js authentication and protected routes guide covers Auth.js v5 and why middleware alone is not a full security model.
What Belongs Elsewhere
Middleware should answer “should this request continue, and with which headers?” It should not answer “what is this user’s invoice total?”
| Job | Better home |
|---|---|
| Form submit / mutations | Server Actions or route handlers |
| Personalized product data | Server Components + your data layer |
| Heavy auth provider logic | Auth library + server session helpers |
| Dynamic product URLs | App Router dynamic routes |
If you need streaming UI or Suspense boundaries, that is layout and page work — see Next.js streaming layouts. Middleware does not replace those tools; it only decides whether the request reaches them.
When Middleware Saves Real Product Time
Teams usually add it after a painful moment. A public URL that should have died last quarter still ranks. A client hits /dashboard logged out and sees a flash of private UI. Or a regional launch needs a soft geo redirect before marketing builds ten more pages.
Those are product bugs as much as code bugs. A two-file middleware fix is cheaper than teaching every page to remember the rule. When the app grows past a brochure site into logged-in workflows, you are in web app territory — middleware is one of the seams that keeps that product from leaking the wrong screen.
If you are wiring redirects and session gates into a Next.js product and want a second pair of eyes on the architecture, book fifteen minutes or start from contact. Bring the routes that must stay private and the URLs you still need to honor.