Skip to content

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

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.

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.

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.