Next.js 16: middleware.ts is now proxy.ts
Next.js 16 renamed the middleware file convention to proxy. middleware.ts still runs, but it logs a deprecation warning, and shipping both files is a hard build error. Migrate with npx @next/codemod@canary middleware-to-proxy . — then check every path-based rule you own, because "middleware.ts" is hardcoded in more of your tooling than you think.
The warning arrives on your first next build after upgrading, and it is exact:
The "middleware" file convention is deprecated. Please use "proxy" instead.
To migrate automatically, run:
npx @next/codemod@canary middleware-to-proxy .
Learn more: https://nextjs.org/docs/messages/middleware-to-proxy
Nothing is broken yet. This is a rename with a deadline attached, and it is worth doing deliberately rather than letting the codemod run and moving on — because the rename changes the runtime, and it quietly changes what your security tooling can see. Other parts of the v16 upgrade do break on the first request: remote images served from localhost or a local Supabase stack now get the "url" parameter is not allowed 400 from next/image, because the image optimizer refuses private IPs by default.
What does "The middleware file convention is deprecated. Please use proxy instead" mean?
That Next.js 16 has renamed the file, the exported function, and the config flags — and that your existing file still works while it warns. The reason is stated plainly in the Next.js Proxy docs: the name middleware
"confuses users with Express.js middleware, which can encourage misuse. To clarify our direction, we are renaming the file convention to 'proxy.' This highlights that we are moving away from Middleware, breaking down its overloaded features, and making the Proxy clear in its purpose."
The version history entry is v16.0.0 — Middleware is deprecated and renamed to Proxy. Proxy defaults to the Node.js runtime.
The migration, exactly
Four changes, and the codemod does the first two:
npx @next/codemod@canary middleware-to-proxy .
1. Rename the file. middleware.ts → proxy.ts, at the project root or inside src/, same level as app or pages. If you use pageExtensions such as .page.ts, the file is proxy.page.ts.
2. Rename the function. The named export middleware is deprecated too:
// proxy.ts
export function proxy(request: NextRequest) { /* … */ }
The docs are specific here: the file must export a single function, either as a default export or named proxy, and multiple proxy exports from one file are not supported. They recommend renaming the function even if you use a default export.
3. Rename the config flags. Anything in next.config.ts carrying the old name moves — skipMiddlewareUrlNormalize becomes skipProxyUrlNormalize, and its siblings follow the same pattern.
4. Keep your matcher. export const config = { matcher: "/about/:path*" } works unchanged, and you should still have one — running on every request is its own class of problem, and it did not get better with the rename.
The trap the codemod cannot fix
Proxy is Node.js-only. From the upgrade guide: runtime is not supported in Proxy, the Proxy runtime is nodejs, and it cannot be configured. If you were running runtime: 'edge', the official advice is to keep using middleware until Vercel ships edge instructions in a later minor.
So if your gate depends on the edge runtime — geolocation at the CDN, sub-millisecond rewrites, an edge KV read — do not run the codemod yet. Read the warning, note it, and revisit. This is the one case where "migrate later" is the documented answer rather than procrastination.
Do not leave the old file behind
A half-finished migration fails the build outright rather than silently preferring one file:
Both middleware file "./middleware.ts" and proxy file "./proxy.ts" are detected.
Please use "./proxy.ts" only.
That is a better outcome than the alternative, so let it stop you. Delete middleware.ts once proxy.ts is in place — and check src/ as well as the root, since only one of the two needs to exist for the error to fire.
The part that matters for security
Here is the failure mode nobody warns you about, and it has nothing to do with Next.js. Your security tooling knows the filename.
Go and count what references middleware.ts by name in your repo and your pipeline: a CODEOWNERS line requiring review on the auth gate, a CI grep asserting the file calls getUser(), an ESLint override, a Semgrep or CodeQL path filter, a test that imports it, a paths: trigger on a GitHub Actions workflow. Every one of those is a path match against a filename you are about to change, and every one fails open — it stops matching, reports nothing, and looks like a pass.
GuardLayer is in that list, and it is worth being blunt about it. Two of its rules are scoped to middleware.ts by path: the config.matcher check, and the one flagging a getSession() auth gate that never revalidates. Here is a file that fires today:
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
export async function middleware(request: NextRequest) {
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ cookies: { getAll: () => request.cookies.getAll(), setAll: () => {} } }
);
const { data: { session } } = await supabase.auth.getSession();
if (!session) return NextResponse.redirect(new URL("/login", request.url));
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*", "/settings/:path*"] };
- Warningmiddleware.ts:11
getSession() trusted in server code
In server code (middleware, route handlers, server actions) authorize with supabase.auth.getUser() — it revalidates the JWT — not getSession(). getSession() is fine on the client, where the session is already trusted.
Rename that file to proxy.ts and the finding disappears. The bug does not: getSession() still decodes the cookie without verifying its signature, so a forged cookie still walks past the gate. Only the detection went away.
That is the whole lesson of this migration. A rename is a rename to the framework and a blind spot to everything that watched the old name. After you migrate, re-run your scanners and expect the output to be identical — if it got quieter, that is your answer.
While you are in that file
Since you are touching the auth gate anyway, it is the right moment to stop treating it as one. The gate in proxy.ts is a redirect for logged-out visitors, not an authorization boundary — that distinction is what CVE-2025-29927 made expensive, and the rename does not change it. Each protected page, route handler and server action needs its own check.
A 60-second self-check
# 1. Is the old file still there?
ls middleware.* src/middleware.* proxy.* src/proxy.* 2>/dev/null
# 2. What in your repo and CI still hardcodes the old name?
grep -rn "middleware\.ts\|middleware\.js" --include="*.yml" --include="*.yaml" \
--include="*.json" --include="*.toml" --include=".eslintrc*" . | grep -v node_modules
# 3. Does the gate revalidate, or just decode?
grep -n "getSession\|getUser\|getClaims" proxy.ts middleware.ts 2>/dev/null
Step 2 is the one worth reading line by line. Step 3 should show getUser() or getClaims(); if it only shows getSession(), fix that before you rename, while your tools can still see it.
FAQ
Does middleware.ts still work in Next.js 16?
Yes. It runs and behaves as before, but next build logs a deprecation warning once per build. It is deprecated, not removed.
What is the difference between proxy.ts and middleware.ts?
The file name, the exported function name (proxy instead of middleware), the config flag names, and the runtime: Proxy runs on Node.js and that is not configurable, whereas middleware could opt into the edge runtime.
How do I migrate automatically?
npx @next/codemod@canary middleware-to-proxy . — it renames the file and the function. You still have to update next.config.ts flags yourself and delete the old file.
Can I keep using the edge runtime?
Not in Proxy. The upgrade guide says runtime is unsupported there and the runtime is nodejs, and it tells you to keep using middleware if you need edge until further instructions land in a minor release.
Can I have both middleware.ts and proxy.ts?
No. Next.js throws a build error naming both files and telling you to keep only the proxy one. Delete the old file, and check src/ as well as the project root.
Does the rename affect my security posture?
Not the framework behaviour — but it can affect your detection. Any path-scoped rule, CI grep, review requirement or scanner check keyed to middleware.ts silently stops matching. Re-run everything after the rename and compare the output to before.
Catch this before it ships — free
Scan your files for this and 35 other Next.js + Supabase issues, with the exact fix inline. Connect GitHub and GuardLayer checks every push too — free on public repos.
No signup, no card. Raw files aren’t kept; only the report is saved.