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 insidesrc/— at the same level asapporpages. 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 ofmiddleware. - The runtime now defaults to Node.js. The
runtimeroute 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/staticassets, image optimization, and files inpublic/. 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
headersandredirectsfromnext.config, and beforebeforeFilesrewrites and filesystem routes. If a simple redirect will do, put it innext.configand keep it out of proxy entirely. - Only one file. Libraries and examples that ship their own
middleware.tsneed to be merged into your singleproxy.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.