The middleware execution model, matcher config, cookies()/headers() write constraints, userAgent() for device routing, draftMode() for CMS preview, and the 1ms performance budget.
P-6 — Middleware: Routing Logic at the Edge
Who this is for: Practitioners who understand the App Router and have used auth in P-4, and now need to work with Middleware directly — understanding its execution model, writing production-grade matchers, and knowing exactly what it can and cannot do. Middleware is powerful but bounded; understanding those bounds is what separates clean Middleware code from subtle bugs.
What Middleware Actually Is
middleware.ts at the project root defines a function that runs on the edge runtime — a V8 isolate, not a full Node.js process — before every matched request. It intercepts the request, can inspect it, modify response headers, set cookies, or redirect, and then either passes the request through or returns a response directly.
The edge runtime has two defining constraints:
-
No Node.js APIs. No
fs, nopath, nocrypto(Web Crypto is available), no native Node.js modules. This is intentional — the edge runtime is designed to be deployable at CDN edge locations globally, not just in a central Node.js server. -
Performance budget: 1ms target. Middleware runs on every matched request, before the page renders. Every millisecond spent in Middleware is added to every page's Time to First Byte. Keep it fast — read cookies, check a JWT, do a redirect. Don't query databases, don't make slow API calls.
The Matcher — Controlling Which Routes Trigger Middleware
Without a matcher, Middleware runs on every request including static files, Next.js internal routes, and image optimization requests. This wastes compute and can cause surprising behaviour. Always define a matcher.
String matcher:
Array matcher:
Regex matcher with negation (most common pattern):
The pattern (?!...) is a negative lookahead — it matches everything except the listed patterns. This is the most robust matcher for production use: it skips static assets entirely, running Middleware only on page routes and API routes.
Conditional logic in the function itself:
Matchers and in-function conditions both work. Matchers are evaluated first and are more efficient because they prevent the function from running at all. In-function conditions are useful for nuanced logic that regex can't express cleanly.
Reading Cookies and Headers
Vercel-specific headers like x-vercel-ip-country, x-vercel-ip-city, and x-vercel-ip-continent give you geo-location data at the edge. Useful for geo-based redirects, A/B tests by region, or content personalisation. On self-hosted deployments, these headers don't exist — use a separate geo-IP service if needed.
Writing Cookies and Headers
Sign in to keep reading
The rest of this module is free — sign in with Google to unlock it and track your progress.
Sign in & RegisterDiscussion
0Join the discussion