afaqrashid.com  ·  portfolio · 2026 ed.

Next.js 16 Middleware to proxy.ts: Why It's Not Working (Tested)

In Next.js 16, middleware.ts became proxy.ts and the exported middleware function became proxy. The old file still works for now, which is why a half-finished migration is the usual cause of "my middleware isn't running".

I tested every combination on Next.js 16.4.4. The proxy set a response header, and I checked with curl whether it was there after next build and next start.

What runs and what doesn't

setup                                         build    proxy runs?
middleware.ts, export function middleware     passes   yes, with a deprecation warning
proxy.ts, export function proxy               passes   yes
proxy.ts, export default function             passes   yes
proxy.ts, export function middleware          fails    -
middleware.ts and proxy.ts both present       fails    -
proxy.ts inside app/                          passes   no, silently
proxy.ts at the root, but the app is in src/  passes   no, silently
src/proxy.ts with src/app                     passes   yes
proxy.ts with runtime: "edge"                 fails    -

The two silent rows are the ones that cost time: the build passed, the build output didn't list a proxy, and no request was touched. When it works, next build lists "ƒ Proxy (Middleware)" at the bottom of the route table. If that line is missing, Next.js didn't find your file.

The old file still works, with a warning

⚠ The "middleware" file convention is deprecated. Please use "proxy" instead.
  npx @next/codemod@canary middleware-to-proxy .
  Learn more: https://nextjs.org/docs/messages/middleware-to-proxy

middleware.ts exporting middleware still ran on every request. You don't have to migrate the day you upgrade, but the warning means it will go away.

Errors from mixing the names

Renaming the file but not the function failed the build:

Error: Proxy is missing expected function export name
This function is what Next.js runs for every request handled by this proxy (previously called middleware).
- You are migrating from `middleware` to `proxy`, but haven't updated the exported function.

Export it as proxy, or as a default export: both ran. Leaving the old file next to the new one also failed: "Both middleware file "./middleware.ts" and proxy file "./proxy.ts" are detected. Please use "./proxy.ts" only."

Where the file has to be

proxy.ts goes in the project root, next to app/ (or pages/). If your app lives in src/app, it goes in src/, next to that app folder. I tried proxy.ts inside app/ and at the root of a src/ project: both builds passed and the proxy never ran.

my-app/                 my-app/
  app/                    src/
  proxy.ts   ✓              app/
                            proxy.ts   ✓

Edge runtime is gone for proxy

Error: Next.js can't recognize the exported `config` field in route. Proxy does not support Edge runtime.

proxy.ts runs on the Node.js runtime; setting runtime: "edge" failed the build. If your middleware relied on edge-only behaviour, or you deploy somewhere that runs middleware at the edge, test it after the move. Setting runtime: "edge" on the old middleware.ts also failed on 16.4.4, with "the edge runtime for rendering is currently experimental. Use runtime 'experimental-edge' instead."

It runs on every request unless you add a matcher

Without a matcher, my proxy ran on the page, on /robots.txt and on the JavaScript chunks under /_next/static. Auth checks or redirects there can break the page's own assets. Limit it to the paths you need:

// proxy.ts
import { NextResponse, type NextRequest } from "next/server";

export function proxy(request: NextRequest) {
  // ...
  return NextResponse.next();
}

export const config = {
  matcher: "/admin/:path*",
};

With that matcher the header appeared on /admin and not on other pages. To run everywhere except assets, the docs use a negative lookahead:

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)"],
};

The codemod

npx @next/codemod@latest middleware-to-proxy .

I ran version 16.4.5 on a middleware.ts that had export const config = { runtime: "edge" }. It renamed the file to proxy.ts and the function to proxy, and it removed the config export entirely. With only a runtime in it that's what you want, but check the diff if your config also had a matcher next to the runtime.

Checklist

  • File: proxy.ts in the root, or in src/ if you use src/app. Not inside app/.
  • Export: function proxy, or a default export. Not middleware.
  • Delete middleware.ts once proxy.ts exists.
  • Look for "ƒ Proxy (Middleware)" in the next build output.
  • Add a matcher so it skips /_next/static and other assets.
  • Remove runtime: "edge"; proxy runs on Node.js.

Related

Sources