Proxy (middleware)
Next 16 renamed middleware.ts to proxy.ts. It runs before the cache and the render, so
everything about it is load-bearing for both correctness and cacheability.
Middleware follows the features, not a package
Section titled “Middleware follows the features, not a package”The proxy composes feature-owned pieces rather than one monolith owning every concern. Each piece belongs to whoever owns the concern:
| Concern | Owned by |
|---|---|
| Locale detection and prefix routing | the routing package |
| Token refresh / session | the auth package |
| Security headers | this site |
Order is the contract
Section titled “Order is the contract”Each piece may short-circuit with a redirect or rewrite, so earlier pieces win. Compose from most foundational to most specific and keep the order in one place — this file — so the chain is readable as a whole. A piece that returns a response stops the chain; otherwise the request falls through to the route.
Locale handling usually goes late, so auth and account redirects resolve first.
Caching is the thing to protect
Section titled “Caching is the thing to protect”Middleware runs per request, and any cookie, session or header it reads to shape the response can push an otherwise-cacheable anonymous route into dynamic rendering and bypass the CDN.
- On anonymous surfaces, keep the proxy to path-level concerns: locale prefix, redirects.
- Per-user reads belong to authenticated requests, which are uncached anyway.
- Never vary the anonymous document on a per-user cookie. Locale and currency vary by path.
Matcher and cost
Section titled “Matcher and cost”Scope the matcher to page requests — exclude _next, static assets and api — so the proxy does
not run on every asset. Keep each piece cheap: it runs before the cache and the render, so heavy
work belongs in the route. Note the runtime constraints of what you write; not everything available
in a route handler is available here.

