COLUMN
Next.js Proxy (formerly Middleware): What Actually Changes When You Self-Host in v16

Middleware just got renamed — and that's not the interesting part
If you've been away from Next.js for a release or two, you'll notice middleware.ts doesn't exist anymore. As of Next.js v16.0.0, the file convention was renamed to proxy.ts, and the exported function is now called proxy instead of middleware. Next.js ships a codemod to do the rename for you:
npx @next/codemod@canary middleware-to-proxy .
That part is cosmetic. The change underneath it is the one that actually matters if you're self-hosting: Proxy now defaults to running on the Node.js runtime, not the Edge runtime. Middleware's Edge-only restrictions were one of the more common reasons a self-hosted Next.js app worked differently than expected in production — and that's the gap this article walks through.
From Edge-only to Node.js by default
Middleware started out Edge-runtime-only — a deliberately restricted execution environment (a subset of Web APIs, no arbitrary Node.js modules) designed to run in many small, geographically distributed locations. Vercel added an opt-in Node.js runtime for Middleware in v15.2 (experimental) and stabilized it in v15.5. With v16, Node.js became the default, and the option to switch runtimes was removed entirely: setting a runtime config in a Proxy file now throws an error, because Proxy always runs in the same Node.js process as the rest of your server.
This is a genuine trade-off, not a pure upgrade. On Vercel, Edge-runtime Middleware ran at edge locations close to each visitor, ahead of your main compute. Node.js-runtime Proxy runs in the same place your app runs — for most self-hosted setups, that was already true, since you're usually deploying to one or a handful of regions rather than a global edge network. If your Proxy logic never leaned on the geographic distribution, the Node.js default is close to a strict win: fewer restricted APIs, no more working around a runtime you didn't choose.

What this fixes for self-hosted teams
The Edge runtime's restricted API surface was a recurring source of friction for anyone self-hosting. Authentication libraries that touch crypto in Node-specific ways, database drivers, or any npm package that assumed a full Node.js environment would work fine inside a Route Handler but fail — or need a rewrite — inside Edge-runtime Middleware. That mismatch didn't buy self-hosted teams anything, since they weren't benefiting from Vercel's edge network in the first place; it was pure tax.
With Proxy on Node.js by default, that tax mostly goes away for logic that needs standard Node.js behavior. The official self-hosting guide confirms Proxy "works self-hosted with zero configuration when deploying using next start," and only flags a fallback (moving logic into a layout as a Server Component, or handling it in next.config.js via header/cookie/query matching) for the rare case where something still doesn't fit. That's a shorter list of exceptions than it used to be.
One thing this change does not bring back: NextRequest.geo and NextRequest.ip. Those were removed in v15.0.0 because they depended on infrastructure only Vercel's edge network provided — there's no way to source true request geolocation without a CDN or platform in front of your servers reporting it. If your Proxy logic self-hosted still needs country- or IP-based routing, you'll be reading it from headers your own reverse proxy or CDN sets, not from a Next.js API.
What stays the same
The parts of Middleware that made it useful are untouched by the rename. NextResponse.redirect, .rewrite, header and cookie manipulation, the matcher config for scoping which routes run — all identical in Proxy. The file-convention reference documents the same NextRequest / NextFetchEvent signature, the same waitUntil() for background work after a response is sent, and the same execution order relative to next.config.js redirects and rewrites.
The codemod's rename is genuinely just a rename — function name and file name, nothing in the body:
// middleware.ts -> proxy.ts
- export function middleware() {
+ export function proxy() {
Your existing matcher array, NextResponse calls, and cookie logic carry over unchanged. If your app doesn't have a runtime override sitting in the Proxy config already, running the codemod and redeploying is close to the entire migration.

Where this collides with running more than one instance
Proxy running on Node.js also means it now shares the same process — and the same constraints — as the rest of your self-hosted deployment. That matters as soon as you run more than one instance. The self-hosting guide is explicit that "if you are hosting Next.js using a container orchestration platform like Kubernetes, each pod will have a copy of the cache," and that Proxy logic, encryption keys for Server Functions, and cache tag invalidation all need to be coordinated across instances rather than assumed to be global. None of this is new to Next.js — it's the same category of problem you hit with migrating a Docker Compose setup to Kubernetes: a single-instance mental model stops matching reality the moment a second replica exists.
This is where the platform choice starts to matter more than the application code. Multiple Next.js instances behind a load balancer, a reverse proxy that doesn't buffer streamed responses, and a cache handler shared across pods — none of that requires Vercel, but it does require something orchestrating restarts, service discovery, and rolling deploys for you. If you're already asking whether to reach for a lighter distribution or a full one, K3s vs. K8s: a use-case-driven selection guide is the right next read before you commit infrastructure.
This is exactly where hand-wiring ingress, TLS, and cache coordination yourself starts to eat the time you saved by not paying for Vercel. Kubo is built for that gap: a managed K3s cluster with that plumbing already in place, closer to Vercel's "it just works" experience than raw self-managed Kubernetes, while staying on standard Kubernetes so nothing is proprietary to one vendor. The same control plane runs whether your pods are in the cloud or on your own hardware.
The takeaway
Middleware's rename to Proxy is easy to shrug off as naming churn, but the default runtime switch to Node.js is a real change in what's practical to run self-hosted — fewer restricted APIs, one less workaround to maintain, at the cost of losing any Edge-specific distribution you might have been relying on (which most self-hosted setups weren't). Run the codemod, check your Proxy file for anything that assumed Edge-only restrictions, and confirm any geo/IP logic already migrated off NextRequest.ip back when v15.0.0 removed it.
From there, the remaining decisions are about your infrastructure, not your Proxy code. If you're a solo developer or running a side project, Kubo's Ashigaru plan starts at ¥8,800/month; most small teams weighing this land on Ronin, from ¥17,600/month. See the current plans and start with the one that matches how many instances you're actually planning to run.