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

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](https://nextjs.org/docs/app/api-reference/file-conventions/proxy):

| | `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:

```ts
// 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:

```text
⚠  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

```bash
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:

```diff
- // 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:

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.

```ts
// 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:

```text
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:

```text
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.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](https://fabwaseem.com/blog/better-auth-nextjs), and the October Next.js patches in [this security update post](https://fabwaseem.com/blog/nextjs-security-update-october-2026) 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](https://fabwaseem.com/blog/nextjs-middleware-to-proxy).*
