DEV Community

Cover image for Next.js Middleware to Proxy: How to Migrate middleware.ts to proxy.ts in Next.js 16
Waseem Anjum
Waseem Anjum

Posted on Originally published at fabwaseem.com

Next.js Middleware to Proxy: How to Migrate middleware.ts to proxy.ts in Next.js 16

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*'],
}
Enter fullscreen mode Exit fullscreen mode

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 .
Enter fullscreen mode Exit fullscreen mode

The route table already labels it ƒ Proxy (Middleware).

Step 2: run the middleware-to-proxy codemod

npx @next/codemod@canary middleware-to-proxy .
Enter fullscreen mode Exit fullscreen mode

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) {
Enter fullscreen mode Exit fullscreen mode

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:

  1. Rename middleware.ts to proxy.ts, in the same place (project root, or src/ if you use it).
  2. 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
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.matcher strings, arrays, regex and has / missing conditions work the same. Matchers must still be constants.
  • Execution order. Proxy runs after headers and redirects from next.config.js and 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. skipMiddlewareUrlNormalize is now skipProxyUrlNormalize. skipTrailingSlashRedirect is unchanged.
  • Testing helpers. The current docs show unstable_doesProxyMatch, but the 16.4.5 package I installed still only exports unstable_doesMiddlewareMatch from next/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 skipMiddlewareUrlNormalize to skipProxyUrlNormalize in next.config (the old name is marked @deprecated in 16.4.5's types)
  • [ ] Re-run next build and 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)