COLUMN
09/11/2026
Next.js 16's Proxy Default: Why Your Matcher Config Matters More Than Before

Next.js 16 quietly changed what happens when a request hits your server before your app code ever runs. The feature used to be called Middleware. As of v16.0.0 it's deprecated and renamed to Proxy — and the new default runtime is Node.js, not Edge. If you're running next start in a container instead of deploying to Vercel, that default is worth understanding before your next upgrade, because it changes how much work your own server does on every single request.
Why Next.js renamed Middleware to Proxy
According to the Next.js team, the old name caused real confusion: developers kept assuming middleware.ts worked like Express.js middleware — a general-purpose place to run arbitrary request-handling logic. It never was. Middleware ran in a constrained runtime, and the team has been explicit that it's "recommended to be used as a last resort," with more and more of its common use cases (auth checks, redirects, header rewrites) pushed toward layouts, Server Components, and next.config.js instead.
The rename makes the scope explicit: "proxy" implies a network boundary in front of your app — logic that runs before your app, not inside it.
Mechanically, the change is small. middleware.ts becomes proxy.ts, and the exported function middleware becomes proxy:
// middleware.ts -> proxy.ts
- export function middleware() {
+ export function proxy() {
Next.js ships a codemod for the rename:
npx @next/codemod@canary middleware-to-proxy .
middleware.ts still works in v16 — it's deprecated, not removed. But if you're self-hosting, you're the one who decides when to take a major version bump, often as a single batched change across a fleet of containers. That's a good moment to run the codemod rather than carry a deprecated file convention into the next upgrade too.
The default runtime flipped — and that's good news for self-hosting
This is the part that matters more than the rename itself. Middleware defaulted to the Edge Runtime: a lightweight, V8-isolate-based environment with no access to most Node.js APIs (no fs, limited crypto, no native modules). Teams self-hosting on their own Node.js servers or Kubernetes pods had full Node.js available everywhere else in their app, except inside middleware — so any logic that needed a database driver, a Node-only SDK, or fs had to be moved out, which is exactly the kind of workaround the self-hosting guide has long pointed around.
Proxy flips the default: it now runs on the Node.js runtime. You get the same APIs your route handlers already have, directly in proxy.ts, with no edge-compatible package hunting.
One thing to know if you're migrating an existing middleware.ts that explicitly opted into Node.js (available as a stable option since v15.5 via export const config = { runtime: 'nodejs' }): that runtime option doesn't exist for Proxy files at all. Setting it throws a build error, because there's nothing left to opt into — Node.js is simply what Proxy does now. Delete the line as part of your migration, don't just rename the function.
The part self-hosted teams actually need to watch: your matcher
Here's the operational detail that's easy to miss in the rename announcement. The Proxy docs now carry an explicit warning that Middleware's docs stated far less directly:
Without a
matcher, Proxy runs on every request, including static files (_next/static), image optimizations (_next/image), and assets in thepublic/folder.

On Vercel, a chunk of this is absorbed by their edge network regardless. When you self-host — behind your own nginx, Traefik, or Kubernetes Ingress, with next start as a single Node.js process (or a small fleet of pods) — there's no separate edge layer quietly intercepting those static and image requests before they reach your app. If your proxy.ts has no matcher, or a matcher that's broader than it needs to be, every one of those requests now executes your proxy function first, on the Node.js runtime, before Next.js even gets to serving the static file. At low traffic this is invisible. At real production traffic, it's extra CPU time and memory pressure on infrastructure you are paying for directly — which is a very different cost conversation than it is on a platform that bills differently for edge versus origin compute. If you're already tracking the infrastructure cost of running that compute yourself, this is one more line item worth checking after an upgrade, not just during initial setup.
The fix is the same negative-matcher pattern Next.js has documented for years — it's just more important to actually use it now that Proxy runs on Node.js by default:
// proxy.js
export const config = {
matcher: [
// Match everything except:
// - api (API routes)
// - _next/static (static files)
// - _next/image (image optimization files)
// - favicon.ico, sitemap.xml, robots.txt (metadata files)
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
One gotcha worth flagging if you rely on this pattern for an auth check: Next.js intentionally still runs Proxy against _next/data routes even when a negative matcher excludes them, specifically to stop a common mistake — protecting a page but forgetting its corresponding data route. If your matcher assumes _next/data is excluded along with the static paths, re-check that assumption after upgrading.
What doesn't change: zero-config self-hosting
The good news is that none of this adds setup work. Per the official self-hosting guide, Proxy "works self-hosted with zero configuration when deploying using next start." The one real constraint carries over unchanged from Middleware: Proxy requires access to the incoming request at request time, so it isn't supported with static export (next export). If you're on a static export today, that was already true before v16 and nothing here changes it.

Rolling this into your next upgrade
Because Proxy now does strictly more by default (full Node.js APIs, same zero-config self-hosting story) while asking you to be more deliberate about matcher, treat this as a checklist item for your next Next.js major-version bump rather than something to defer indefinitely:
- Run
npx @next/codemod@canary middleware-to-proxy .to rename the file and function. - Delete any
runtime: 'nodejs'config left over from the v15.5 opt-in — it now throws in Proxy files. - Review your
matcher(or add one if you never had one) with the negative-match pattern above, and double-check any logic that assumed_next/datawas excluded. - If you renamed
skipMiddlewareUrlNormalizeor similar flags innext.config.js, update them too — it's nowskipProxyUrlNormalize(skipTrailingSlashRedirectkeeps its name).
If you're running this across multiple pods behind a load balancer, this is also a good moment to confirm you'd actually notice a regression — watch request latency and error rates through the rollout rather than assuming the rename is a no-op. Kubo gives you standard Kubernetes for exactly this kind of self-hosted Next.js deployment, with the plumbing for metrics and autoscaling already wired in rather than something you bolt on after a bad upgrade.
When you're ready to put real monitoring behind a self-hosted rollout like this, our guide to monitoring Kubernetes workloads with Prometheus covers the metrics worth alerting on before you need them.