Next.js Proxy Replaces Middleware: What Changes in Practice

Hero image for Next.js Proxy Replaces Middleware: What Changes in Practice. Image by Logan Voss.
Hero image for 'Next.js Proxy Replaces Middleware: What Changes in Practice.' Image by Logan Voss.

In Brief

The move from Middleware to Proxy in Next.js 16 is more than a filename change. proxy.ts makes the request boundary clearer: redirects, rewrites, headers, cookies, and early responses belong there only when they genuinely need requesttime context. Static redirects, routelevel logic, runtime assumptions, and Pages Router behaviour in older estates still need separate review.

Next.js 16 made a naming change that is easy to dismiss as cosmetic. middleware.ts is deprecated, proxy.ts is the new convention, and the feature now has a name that better describes where it sits in the request lifecycle.

That matters because the old name encouraged some misleading mental models. Middleware sounds like a general application layer. Proxy sounds like a network boundary in front of the route. That is much closer to how the feature behaves, and it makes the tradeoffs easier to discuss.

This is also the current counterpart to my older note on Next.js Middleware being unavailable with Pages Router. That earlier article should be read as versionspecific debugging context for Next.js 15.5.5. This one is about the Next.js 16 direction and what I would check before relying on Proxy in production.


The Rename is a Design Signal

The Next.js 16 release notes describe Proxy as the replacement for Middleware. The current Proxy documentation also makes the new file convention explicit: create proxy.ts or proxy.js at the project root, or inside src at the same level as pages or app.

The important part is not just the filename. It is the intent. Proxy runs before routes are rendered and can redirect, rewrite, adjust request or response headers, work with cookies, or produce a response directly. That is powerful, but it is still request interception. It is not a general place to hide product logic.


What Changed in Next.js 16

The practical migration has three parts. Rename the file from middleware.ts to proxy.ts, rename the named export from middleware to proxy, and update middlewarenamed configuration flags where they apply. For example, skipMiddlewareUrlNormalize became skipProxyUrlNormalize.

The Next.js 16 upgrade guide also points to a codemod, npx @next/codemod@canary middleware-to-proxy ., for the mechanical part. I would still review the result by hand because a codemod can rename files and exports, but it cannot decide whether Proxy is the right boundary for the logic.

There is one runtime detail worth treating carefully. In the Next.js 16 upgrade notes, Proxy runs on the Node.js runtime and the runtime cannot be configured there. If an existing application depends on the old Edge runtime behaviour, that is not a small naming migration. It is a platform decision that needs testing against the current release notes and hosting environment.


What Did Not Really Change

The broad job is still familiar. Proxy can inspect the incoming request and decide whether to continue, redirect, rewrite, alter headers, set cookies, or respond early. The matcher still matters because Proxy can otherwise apply much more broadly than intended. A loose matcher is one of the easiest ways to turn a neat central file into a debugging problem.

The same judgement also applies to redirects. If the rule is static and does not need request data, start with redirects in next.config.ts. Proxy earns its place when it needs request context, such as a cookie, header, host, locale, experiment bucket, or other signal that is only known when the request arrives.


Pages Router Needs Separate Context

The Pages Router point is the awkward one. The current docs include Proxy in the Pages Router filesystem convention, but that does not erase real project history. If a Pages Router application had Middleware that never fired in a specific Next.js 15 setup, migrating the filename alone is not enough evidence that the behaviour is now healthy.

For older Pages Router estates, I would treat Proxy as something to prove in the target version rather than something to assume from documentation. Add a tiny test redirect, log at the boundary, verify the matcher, check pageExtensions if the project uses custom page suffixes, and confirm behaviour in the same deployment model that production uses.

That is the distinction the old article was missing. It described a genuine Next.js 15.5.5 failure mode, but the current conversation should be framed around Proxy in Next.js 16 and tested against the project actually being shipped.


Where Proxy is Useful

Proxy is a good fit when the decision is lightweight, requestwide, and genuinely belongs before rendering. Locale routing, hostbased redirects, simple cookie checks, experiment routing, canonical redirects, and small header decisions can all fit that shape.

It is also useful for migration work. If a project is consolidating paths, moving sections, or absorbing a legacy system, Proxy can keep routing decisions close to the network boundary while the application code catches up. That does not make it free, but it can be a clean temporary tool.


Where Proxy Becomes the Wrong Tool

The trouble starts when Proxy becomes a policy engine. Full authorisation checks, databasebacked entitlement rules, content decisions, analytics enrichment, and routespecific business logic usually need more context than this boundary should own. The code might be short, but the behaviour becomes harder to reason about.

The current docs also warn against leaning on Proxy unless no better option exists. That is not a throwaway warning. If the logic can live in a route handler, server component, server action, API route, platform redirect, or explicit application boundary, that is often easier to test and easier to explain later.

I have written more about that decision point in When Next.js Middleware Is the Wrong Tool. The naming has changed, but the architectural point is the same: early request interception should stay small enough that engineers can still predict the system without replaying a hidden rules engine in their heads.


What to Check Before Shipping

Before I would ship a Proxy migration, I would check five things.

  • Does the matcher include exactly the routes that need interception, including data and prefetch behaviour where relevant?
  • Does the logic need requesttime data, or could it be a static redirect, route handler, or serverside check?
  • Does the application depend on Edge runtime behaviour that the Proxy migration changes?
  • Is there a small test that proves the Proxy runs for the important paths and does not run for the excluded ones?
  • Can a new engineer explain the responsibility of the file in one sentence?

That last question sounds soft, but it is usually the useful one. If the file is hard to explain, it is probably doing too much.


Wrapping Up

Next.js Proxy is not a brandnew superpower. It is the renamed and reframed version of Middleware, with a clearer emphasis on request interception at the network boundary. That naming is useful because it nudges teams away from treating the feature as a general application layer.

The migration itself can be mechanical, but the decision should not be. Rename the file, update the export, review the runtime expectations, and then ask whether the logic still belongs there. If the answer is yes, Proxy can be a tidy, explicit boundary. If the answer is no, the rename has done its job by making the overreach easier to see.

Key Takeaways

  • Next.js 16 deprecates the middleware.ts convention in favour of proxy.ts.
  • Proxy is still request interception before route rendering, not a general application middleware layer.
  • Pages Router projects should verify behaviour in their target Next.js version and deployment model.
  • Use Proxy for small requesttime decisions, and keep richer application logic closer to the route or server boundary that owns it.

Need a senior engineer involved?

I can work directly in the codebase, review the architecture, or support the team through delivery when the work needs more than extra hands.