Module P-6·23 min read

The middleware execution model, matcher config, cookies()/headers() write constraints, userAgent() for device routing, draftMode() for CMS preview, and the 1ms performance budget.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

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:

  1. No Node.js APIs. No fs, no path, no crypto (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.

  2. 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.

ts

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:

ts

Array matcher:

ts

Regex matcher with negation (most common pattern):

ts

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:

ts

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

ts

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 & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.