middleware-patterns

Installation
SKILL.md

Middleware Patterns

Next.js 16 rename. The root request-preprocessing file is now proxy.ts (exported function proxy). The middleware.ts/middleware convention is deprecated — it still works (a build warning, no error) and is retained specifically for Edge Runtime use cases, but Vercel's recommended path is proxy.ts. This skill uses "proxy/middleware" for the shared concept and names the file explicitly when the distinction matters. If you are on Next.js 15 or earlier, everything here applies under the middleware.ts name. See § The File Contract and § Migrating middleware.ts → proxy.ts.

Concept of the skill

The Next.js request-preprocessing layer is a single async function exported from one root file that runs before route resolution for every request matching its matcher config — proxy.ts (function proxy, Node.js runtime) since Next.js 16, or the deprecated-but-retained middleware.ts (function middleware, Edge Runtime by default) on older versions and Edge-only use cases. It receives a NextRequest and returns a NextResponse in one of four shapes: pass through (next, optionally with modified headers/cookies), rewrite (serve different content under the same URL), redirect (send a 30x and change the URL bar), or a direct response (short-circuit with a body and status). Because it is the only place a request can be intercepted before the framework knows which route it will hit, it is the right home for genuinely cross-cutting concerns — auth UX gates, locale routing, A/B rewrites, per-request CSP nonces, request-ID correlation, bot blocking, geo-routing — applied across many routes through one matcher instead of duplicated per route. Three facts make or break correct use: the runtime is load-bearing (proxy on Node.js has full APIs but the per-matched-request performance budget still forbids per-request I/O; middleware.ts on Edge has a hard capability ceiling and a bundle limit), the matcher is not a security perimeter (and /_next/data runs even when excluded while a matcher exclusion silently skips Server Functions on that path), and — most importantly — this layer is not an authorization boundary (CVE-2025-29927 let attackers bypass it entirely, and a cluster of May-2026 transport-variant bypasses reinforced that authz must live in the route, the Server Action, and the Data Access Layer, with the proxy auth check being only a fast UX redirect). The Next.js 16 rename from middleware to proxy itself encodes the discipline: keep this layer small, fast, cross-cutting, and never the sole gate — and reach for native next.config redirects/headers, the platform WAF, and route-level auth first.

Coverage

The discipline of designing the Next.js request-preprocessing layer: the one-file-per-project contract (proxy.ts since Next.js 16, or the deprecated middleware.ts, at the root or under src/, single function export), the runtime split (proxy runs on Node.js and its runtime config throws; middleware.ts runs on Edge with the Edge capability ceiling) and what each runtime can and cannot do, the matcher config that filters which paths trigger the layer (including the _next/data-always-runs and Server-Function-coverage gotchas), the NextRequest / NextResponse API surface (cookies, headers, and geolocation()/ipAddress() from @vercel/functions since request.geo/request.ip were removed in Next.js 15), the four response shapes (next, rewrite, redirect, direct response), waitUntil for background work, the official next/experimental/testing/server test utilities, the canonical pattern library (authentication UX gate, locale routing, A/B testing, security header injection, geo-routing, bot blocking, request-id correlation), the performance discipline that every matched request pays the cost, the CVE-2025-29927 lesson that this layer is not a security boundary, and the central design rule: it is for cross-cutting concerns that apply across many routes — never for per-route business logic.

Philosophy of the skill

The Pages Router's request lifecycle was: server hits getServerSideProps, which returns props, which render the page. The App Router added more layers (Server Components, Server Actions, Route Handlers), but kept one thing constant — they all run after the route is resolved.

The proxy/middleware layer runs before. It is the only layer where you can intercept a request without knowing which route it will eventually hit. That makes it the right home for concerns that apply across the entire app or large subsets of it: "every request needs a request-id header", "every request to /admin/* needs a role check", "every page needs a CSP nonce".

Next.js 16's rename from middleware to proxy encodes a deliberate stance. The team renamed it because "middleware" invited Express-style "do all my server logic here" misuse, and because the feature behaves as a network proxy in front of the app. Vercel now frames it as a feature to "use as a last resort" — reach first for native next.config redirects/headers, route-level auth, and the Data Access Layer. The architectural trade is breadth for power. The layer:

Installs
4
First Seen
May 18, 2026
middleware-patterns — jacob-balslev/skills