Next.js 16's proxy.ts: What Replaced Middleware and Why It Matters
Middleware Is Dead. Long Live proxy.ts.
Next.js 16 introduced one of the more controversial renames in the framework's history. The file you knew as middleware.ts is now proxy.ts. The change is not just cosmetic. The internals shifted, the runtime semantics tightened, and the mental model the team wants you to hold has evolved.
The reason the rename matters is that "middleware" was a misleading name from the start. Next's middleware never ran in the request-response chain the way Express middleware does. It always ran as an edge proxy — intercepting requests before they reached the renderer. The new name simply tells the truth about what the file is. proxy.ts. It is a proxy. It runs at the edge. It can rewrite, redirect, or pass through. That is it.
What Actually Changed at the Runtime Level
Beyond the rename, three concrete things changed. First, the file resolution itself — Next.js looks for proxy.ts in the project root or src directory, and the build pipeline will warn if it finds a stale middleware.ts file (it will not silently load it). Second, the runtime guarantees are stricter. The proxy now runs exclusively on the edge runtime. There is no Node runtime fallback. If you were relying on Node-only APIs in your middleware, that code needs to move into a route handler.
Third, the matcher syntax has been clarified. The matcher config still exists, but the team has documented the precedence rules more rigorously, and there is a new exclude pattern that lets you skip the proxy for specific paths cleanly without writing inverse regex. For applications using Clerk, NextAuth, or any auth provider that wraps the middleware export, the migration is mostly a rename. For applications that did anything custom, the upgrade requires real attention.
Diagnosing the Migration — A Real Example
On a production project we shipped recently, the proxy.ts migration was diagnosed by reading the Next.js dist source for the PROXY_FILENAME constant rather than guessing what the team intended. The official migration docs exist, but they don't always cover the edge cases. When in doubt, the source is authoritative — and reading the dist code is the same level of difficulty as reading any other TypeScript module.
The specific issue we hit: a stale middleware.ts was being imported by build tooling that hadn't been updated to look for proxy.ts. The build was completing, but the auth layer was silently disabled in production until we caught it on a security review. The lesson: when migrating major framework versions, search the entire codebase for the old filename, not just the obvious imports. Tooling, scripts, deploy configs, and CI files often reference things you forgot existed.
The Auth Pattern in proxy.ts
For most applications, the highest-stakes use of proxy.ts is auth gating. The pattern that has emerged is consistent across the major auth providers. You import the auth library's middleware factory, configure it with your protected routes, and export the result as the default. The factory handles session validation, redirects to the sign-in page, and pushes auth context into request headers that downstream route handlers can read.
The Clerk pattern in 2026 looks like this: clerkMiddleware() with explicit publicRoutes and ignoredRoutes. The proxy resolves the user's session at the edge, sets the auth headers, and the App Router uses Server Components to read auth() from those headers without an additional round trip. This is dramatically faster than the old SPA-style auth where every protected route had to make a separate auth check on the client. Edge auth is one of the genuine wins of the modern Next.js architecture.
What proxy.ts Should and Should Not Do
The temptation with proxy.ts is to put too much in it. Resist. The proxy runs on every request that matches its config. Heavy logic at the edge means heavy latency on every request. The rule we follow: the proxy should do auth gating, locale detection, A/B test bucketing, and feature flag evaluation. That is it. Everything else belongs in route handlers, server actions, or React Server Components.
Specifically, do not call your database from the proxy. Do not make external API calls. Do not parse request bodies in non-trivial ways. The edge runtime has aggressive timeouts and limited compute. A slow proxy degrades every page in your application simultaneously. We have seen production sites with proxy code that adds 200ms to every request. Multiply that by every page view across thousands of users and you have an avoidable infrastructure tax.
The Forward-Looking Pattern
The architectural direction Next.js is signaling with the proxy.ts rename is clear. The framework wants to separate the things that need to run before rendering (proxy: auth, redirects, rewrites) from the things that happen during rendering (Server Components, route handlers). That separation is healthy. It pushes builders toward smaller, more focused middleware that does less, faster.
If you are starting a new Next.js 16 project today, write your proxy.ts with the assumption that anything you put in it runs on every protected request. Audit it like you would audit a hot path. Use composable middleware factories from your auth and feature-flag libraries rather than writing custom logic. And when in doubt, push the work into a route handler where the runtime has more headroom and the failure modes are easier to debug. The proxy is not where your application lives. It is the gate your application sits behind.
Ready to put this into action?
We build the digital infrastructure that turns strategy into revenue. Let's talk about what DRTYLABS can do for your business.
Get in Touch