← Blog

Next.js 16 renamed middleware to proxy.ts: what actually changed, and what to stop doing there

Next.js 16 renamed middleware to proxy.ts and made Node.js its default runtime. The codemod, the matcher traps, and the jobs proxy should not take on.

If you upgraded to Next.js 16 and your middleware.ts started producing deprecation warnings, nothing is broken — the file convention was renamed. Middleware is now Proxy: same capabilities, new file name, new function name, and a different default runtime. The rename itself takes one command. The more useful part is what the Next.js team says the rename is for, because it changes how much work belongs in that file.

What changed

Per the Next.js 16 reference, the middleware file convention is deprecated and renamed to proxy. Concretely:

  • The file is proxy.ts (or .js), in the project root or inside src/ — at the same level as app or pages. Only one proxy file is supported per project; split logic into modules and import them into it if it grows.
  • The function is exported as proxy (named or default) instead of middleware.
  • The runtime now defaults to Node.js. The runtime route segment option is not available in proxy files, and setting it throws an error.

Next.js ships a codemod that does the mechanical part — renaming the file and the function:

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

After that, a proxy looks like the kit's, which bounces signed-out visitors away from the dashboard:

export function proxy(request: NextRequest) {
  const sessionCookie = getSessionCookie(request);
  if (!sessionCookie) {
    const signIn = new URL("/sign-in", request.url);
    signIn.searchParams.set("redirect", request.nextUrl.pathname);
    return NextResponse.redirect(signIn);
  }
  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*"],
};

Why it is called proxy now

The reference gives two reasons, and the second one matters more. First, "middleware" invited confusion with Express middleware, where it is normal to stack layers of application logic. Second, the Next.js team describes this feature as something to use as a last resort, and says it is moving toward better APIs so that apps need it less. "Proxy" names what it actually is: a network boundary in front of your app that sees a request before the app does — good for redirects, rewrites, and header changes, not a place to run your business logic.

That is also why the docs say proxy is not intended for slow data fetching and should not be your full session-management or authorization layer. Use it for cheap, optimistic checks — like the cookie check above — and do the real check where the data is served. How that split works with Better Auth is in the Better Auth setup gotchas.

The matcher is where it goes wrong

  • No matcher means every request. Without a matcher, proxy runs on every request — including _next/static assets, image optimization, and files in public/. An auth redirect without a matcher can quietly block your CSS and JavaScript from loading. Scope it to the paths that need it, as the kit does with /dashboard/:path*.
  • Matchers must be constants. They are analyzed statically at build time, so a matcher built from a variable is ignored.
  • Server Functions ride along with their route. The reference points out that Server Functions are handled as POST requests to the route where they are used — so a matcher that excludes a path also skips the Server Function calls on that path. Move a Server Function to another route, and proxy coverage can disappear silently. Check authentication inside each Server Function, not only in proxy.

Things that bite

  • Do not move slow work in because the runtime changed. Node.js being the default makes more libraries available in proxy; it does not make proxy the right place for database queries. The guidance is still optimistic checks and routing decisions.
  • Order of execution. Proxy runs after headers and redirects from next.config, and before beforeFiles rewrites and filesystem routes. If a simple redirect will do, put it in next.config and keep it out of proxy entirely.
  • Only one file. Libraries and examples that ship their own middleware.ts need to be merged into your single proxy.ts, not dropped alongside it.

Shipwright — the Next.js and Claude starter kit this blog documents — is built on Next.js 16 with a scoped proxy.ts doing only the optimistic cookie check, while the dashboard and every API route verify the session themselves. Sign in from the live demo to see the flow.