Cross-posted from my blog. The original, with any updates, lives on fabwaseem.com.
In Next.js 16, middleware.ts is deprecated and renamed to proxy.ts. Your existing middleware still runs, but every build now prints a deprecation warning, and a few things behave differently: the function name changes, Proxy always runs on the Node.js runtime, and having both files in a project fails the build. This guide covers the Next.js middleware to proxy migration step by step, with the exact warnings and errors I got from Next.js 16.4.5 on a small test app, so you know what to expect before you run the codemod.
Next.js middleware vs proxy: what actually changed
The behavior is the same feature under a new name. From the Next.js proxy docs:
middleware.ts (Next.js 12–15) |
proxy.ts (Next.js 16+) |
|
|---|---|---|
| File name | middleware.ts |
proxy.ts |
| Exported function |
middleware (or default) |
proxy (or default) |
config.matcher |
same | same |
NextRequest / NextResponse
|
same | same |
| Runtime | Edge by default, Node.js opt-in since 15.5 | Node.js only; runtime config throws |
| Status in 16.x | deprecated, still runs with a warning | recommended |
The Next.js team says they renamed it because "middleware" was getting confused with Express-style middleware and encouraged using it for too much. "Proxy" describes what it is: a layer in front of your routes that can redirect, rewrite, set headers or return a response.
Step 1: see the deprecation warning
I started with a typical auth-redirect middleware on Next.js 16.4.5:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const session = request.cookies.get('session')?.value
if (!session) {
const url = new URL('/login', request.url)
url.searchParams.set('next', request.nextUrl.pathname)
return NextResponse.redirect(url)
}
const headers = new Headers(request.headers)
headers.set('x-user', session)
return NextResponse.next({ request: { headers } })
}
export const config = {
matcher: ['/dashboard/:path*'],
}
next build succeeds, but prints:
⚠ The "middleware" file convention is deprecated. Please use "proxy" instead.
To migrate automatically, run:
npx @next/codemod@canary middleware-to-proxy .
The route table already labels it ƒ Proxy (Middleware).
Step 2: run the middleware-to-proxy codemod
npx @next/codemod@canary middleware-to-proxy .
On my test app the codemod renamed middleware.ts to proxy.ts and the exported function from middleware to proxy. Nothing else changed:
- // middleware.ts
- export function middleware(request: NextRequest) {
+ // proxy.ts
+ export function proxy(request: NextRequest) {
After that, next build ran with no deprecation warning. I checked it with next start: /dashboard without a cookie returned a 307 to /login?next=%2Fdashboard, and with session=waseem the page rendered the x-user header the proxy set.
Commit the codemod change on its own so the diff stays easy to review.
Step 3: migrate by hand (if you can't run the codemod)
It's two edits:
- Rename
middleware.tstoproxy.ts, in the same place (project root, orsrc/if you use it). - Rename the exported function to
proxy, or use a default export.
// proxy.ts
import type { NextProxy } from 'next/server'
export const proxy: NextProxy = (request, event) => {
// same logic as before
}
The NextProxy type infers request as NextRequest and event as NextFetchEvent, so event.waitUntil() for background logging still works. If you customized pageExtensions, the file name follows it, for example proxy.page.ts.
Errors you may hit during the migration
"Both middleware file and proxy file are detected"
If the old file is still there after you create proxy.ts, the build fails:
Error: Both middleware file "./middleware.ts" and proxy file "./proxy.ts" are detected.
Please use "./proxy.ts" only.
Delete middleware.ts. This one bites when a merge brings the old file back from another branch.
"Proxy does not support Edge runtime"
Proxy always runs on Node.js. If your old middleware had runtime: 'edge' in its config and you carry it over, the build fails:
Error: Next.js can't recognize the exported `config` field in route.
Proxy does not support Edge runtime.
Remove runtime from the config. For context, keeping middleware.ts with runtime: 'edge' on 16.4.5 also failed, asking for 'experimental-edge', and that value builds but warns that the Edge Runtime is deprecated. Either way, the direction is Node.js.
Running on Node.js is mostly good news: you can use Node APIs and regular npm packages (database clients, jose, crypto) in Proxy. The cost is that you can't rely on Edge-only behavior, so if you self-host behind your own server this changes nothing, and on platforms that ran middleware at the edge you should check where Proxy runs.
What doesn't change
-
Matchers.
config.matcherstrings, arrays, regex andhas/missingconditions work the same. Matchers must still be constants. -
Execution order. Proxy runs after
headersandredirectsfromnext.config.jsand before filesystem routes. -
Request header pattern. Use
NextResponse.next({ request: { headers } })to pass data to your app.NextResponse.next({ headers })sends them to the browser instead. -
Config flags.
skipMiddlewareUrlNormalizeis nowskipProxyUrlNormalize.skipTrailingSlashRedirectis unchanged. -
Testing helpers. The current docs show
unstable_doesProxyMatch, but the 16.4.5 package I installed still only exportsunstable_doesMiddlewareMatchfromnext/experimental/testing/server, so check your installed version before renaming it in tests.
Don't use proxy.ts as your only auth check
The Next.js docs point out that Server Functions are handled as POST requests to the page that uses them, so a matcher that excludes a path also skips Server Function calls on that path. Moving a Server Function or changing a matcher can silently remove Proxy coverage. Treat Proxy as the fast redirect for signed-out users, and check the session again inside each Server Action, Route Handler and data function.
I go through that pattern with a real auth library in Better Auth with Next.js, and the October Next.js patches in this security update post are a good reminder of why defense in depth matters.
Migration checklist
- [ ] Run
npx @next/codemod@canary middleware-to-proxy .or rename the file and function by hand - [ ] Delete any leftover
middleware.ts - [ ] Remove
runtime: 'edge'from the Proxy config - [ ] Rename
skipMiddlewareUrlNormalizetoskipProxyUrlNormalizeinnext.config(the old name is marked@deprecatedin 16.4.5's types) - [ ] Re-run
next buildand confirm the warning is gone and the route table shows Proxy - [ ] Re-check auth inside Server Actions and Route Handlers, not only in Proxy
FAQ
Is Next.js middleware deprecated?
Yes. In Next.js 16.0.0 the middleware file convention was deprecated and renamed to proxy. On 16.4.5, middleware.ts still runs, but next build prints a deprecation warning that points to the codemod.
What is the difference between middleware.ts and proxy.ts in Next.js?
The file name and the exported function name. The API (NextRequest, NextResponse, config.matcher) is the same. The one behavior change is that Proxy always runs on the Node.js runtime and setting runtime in its config is an error.
How do I migrate from middleware to proxy in Next.js 16?
Run npx @next/codemod@canary middleware-to-proxy .. It renames middleware.ts to proxy.ts and the middleware function to proxy. Then delete any leftover middleware.ts, remove runtime: 'edge' if present, and rebuild.
Can I keep both middleware.ts and proxy.ts?
No. On Next.js 16.4.5 the build fails with "Both middleware file and proxy file are detected. Please use ./proxy.ts only."
Does Next.js proxy run on the Edge runtime?
No. Proxy uses the Node.js runtime, and the build fails with "Proxy does not support Edge runtime" if you set runtime: 'edge' in its config.
Originally published on fabwaseem.com.
Top comments (0)