Next.js middleware.ts to proxy.ts: What Changes
Next.js 16 renamed middleware.ts to proxy.ts, and it's not just a name change. Here's what the runtime switch means and how to migrate a real auth-check file.
On this page
Next.js 16 renamed middleware.ts to proxy.ts. If you skimmed a "what's new in Next.js 16" roundup, you probably saw this mentioned in a single bullet point next to Cache Components and Turbopack. It deserves more attention than that, because the rename comes bundled with a real architectural change: proxy.ts runs on the Node.js runtime by default, not the Edge runtime that middleware.ts used.
This guide covers exactly what changes, why the runtime default matters, and how to migrate a real middleware file with auth checks and redirects, step by step.
The Rename Isn't Cosmetic
Three things change together when you move from middleware.ts to proxy.ts, and only one of them is cosmetic:
- The filename —
middleware.tsbecomesproxy.tsat the project root - The exported function name — the named export
middlewarebecomes a default exportproxy - The default runtime — Edge becomes Node.js
The third item is the one that actually matters for your code. middleware.ts still works. Next.js's own release notes confirm it remains available for Edge-runtime use cases, but it's marked deprecated and scheduled for removal in a future major version. This isn't a forced migration today, but it is a deprecation clock worth planning around, especially if your app has more than one middleware-adjacent file.
Why the Default Runtime Change Matters
Edge runtime gave middleware.ts global low-latency distribution: your logic ran close to the requesting user, everywhere, but with a restricted set of available APIs. No arbitrary Node built-ins, no native modules, and a smaller compatibility surface for third-party packages.
Node.js runtime gives proxy.ts the full Node.js API surface. That removes some of the awkward workarounds Edge runtime forced on you, like swapping a package for a lighter Edge-compatible fork just so your middleware would build. The tradeoff runs the other way too: you lose some of the distribution characteristics Edge provided by default.
| Edge Runtime (middleware.ts) | Node.js Runtime (proxy.ts default) | |
|---|---|---|
| Global distribution | Runs at edge locations close to the user | Runs in your deployment's Node.js runtime |
| Node.js API access | Restricted subset of Web APIs only | Full Node.js API surface |
| Package compatibility | Many Node-only packages fail to build | Broad compatibility with npm packages |
| Typical use case fit | Simple, latency-sensitive checks (geo, A/B routing) | Anything needing full Node APIs or heavier logic |
| Still available in Next.js 16? | Yes, via middleware.ts (deprecated) | Yes, the new default via proxy.ts |
If your middleware does something simple like checking a cookie, redirecting unauthenticated users, or rewriting a header, this distinction barely shows up in practice. If your middleware specifically depended on Edge runtime's geographic distribution for latency-sensitive logic, or used Edge-only Web APIs that don't behave identically under Node.js, that's the part to test carefully before migrating. If you're also cleaning up how your app reads configuration during this upgrade, it's worth revisiting how environment variables work in Next.js at the same time, since middleware is one of the few places server-only and public variables both come into play at request time.
Migrating middleware.ts to proxy.ts: Step by Step
- 1
Rename the file
Rename
middleware.tsat your project root toproxy.ts. The file's location doesn't change, only the name. Next.js looks forproxy.tsfirst and falls back tomiddleware.tsif it's not present, so having both at once will cause Next.js to preferproxy.ts.bashmv middleware.ts proxy.ts - 2
Rename the export and switch to a default export
Change the named export
middlewareto a default export namedproxy. This is the part that trips people up if they only skim the docs: it's not just a rename, the export style changes from named to default.typescript// Before export function middleware(request: NextRequest) { ... } // After export default function proxy(request: NextRequest) { ... } - 3
Confirm imports and config are untouched
NextRequestandNextResponseimports don't change. Yourmatcherconfig, if you have one, is exported the same way it always was. The migration is deliberately narrow: file name, function name, export style. Everything else in a typical auth or redirect middleware stays identical. - 4
Test the route locally before deploying
Run
next devand exercise every path your matcher covers: an authenticated request, an unauthenticated one, and any redirect or rewrite branch. For most auth-check or header-rewrite middleware, this is where the migration ends. If your file only touches cookies, headers, and redirects, you're done.bashnext dev # then manually hit the protected routes covered by your matcher
A Real Example: Migrating Auth-Check Middleware
Here's a realistic middleware.ts file that checks a session cookie and redirects unauthenticated users away from a protected route:
import { NextRequest, NextResponse } from 'next/server'
export function middleware(request: NextRequest) {
const session = request.cookies.get('session')
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}The Next.js 16 equivalent, after migration:
import { NextRequest, NextResponse } from 'next/server'
export default function proxy(request: NextRequest) {
const session = request.cookies.get('session')
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}Notice what didn't change: the NextRequest/NextResponse imports, the cookie-checking logic, and the matcher config export. What changed: the filename, the function name, and export default in place of a named export function. For middleware this simple, the migration genuinely is a five-minute rename-and-test.
What Breaks If You Used Edge-Only APIs
If your middleware does anything beyond cookie and header logic, don't migrate blindly. A handful of patterns are common enough to call out specifically before you move a real production file.
| If your middleware does this | Risk under proxy.ts's Node.js runtime | What to do |
|---|---|---|
| Reads geo-IP headers for latency-sensitive routing | Behavior may differ since the code no longer runs at the edge location closest to the user | Benchmark latency on proxy.ts before switching production traffic |
| Imports an Edge-only or WASM-based library | May now work under Node.js, or may need a different package entirely | Test the import path directly rather than assuming compatibility either way |
| Uses `process.env` dynamic key access | No change in behavior; this was already a Next.js compile-time limitation, not an Edge-specific one | No action needed for this specific concern |
| Streams a response body directly from middleware | Node.js runtime handles streaming differently than Edge runtime internals | Manually verify streamed responses in a staging environment |
| Connects directly to a database or external service | Was likely already awkward under Edge; Node.js runtime removes most of that friction | This is usually a safe case to migrate, and often an improvement |
Should You Migrate Now or Wait?
The answer depends entirely on what your middleware actually does, not on how new proxy.ts is.
- Middleware only checks cookies, headers, or does simple redirects: migrate now
- Middleware relies on Edge-specific geo-distribution for latency: benchmark on proxy.ts in a branch first
- Middleware imports a package that previously required an Edge-compatible fork: test the standard package under proxy.ts
- You're mid-migration and can't test every code path today: stay on middleware.ts, it's still supported
- You're starting a new project on Next.js 16: use proxy.ts from day one, there's no reason to start on the deprecated path
Simple middleware, meaning auth checks, redirects, header rewrites, A/B test cookie routing, should migrate now. It's a small, low-risk change and gets you off the deprecation path early. Complex middleware relying on Edge-specific behavior should get tested on proxy.ts in a branch first, with latency benchmarked if that mattered to your use case, and only promoted to production once behavior is confirmed to match. Staying on middleware.ts during that evaluation window is the safer choice since it's explicitly still supported, just deprecated.
If this migration is part of a broader Next.js 16 upgrade, it's also worth checking your CI build pipeline for anything that references middleware.ts by name, like custom lint rules or file-existence checks, so the rename doesn't silently break a build step nobody's looking at. And if you're evaluating whether to stay on Next.js at all for a given project, our Next.js to TanStack Start migration breakdown covers the tradeoffs from the other direction.
Frequently Asked Questions
Is middleware.ts deprecated in Next.js 16?
Yes, but it still works. Next.js 16 introduces proxy.ts as the replacement and continues to support middleware.ts for Edge-runtime use cases during a deprecation window. It's scheduled for removal in a future major version, not the current one.
What is proxy.ts in Next.js?
proxy.ts is the Next.js 16 replacement for middleware.ts. It lives at the project root, exports a default function named proxy instead of a named middleware function, and runs on the Node.js runtime by default instead of the Edge runtime.
- Same purpose — intercepts requests before they reach a route, for auth checks, redirects, and header rewrites
- Different export — default export
proxy, not named exportmiddleware - Different default runtime — Node.js instead of Edge
Does proxy.ts run on Edge or Node.js runtime?
proxy.ts runs on the Node.js runtime by default. This is the core behavioral difference from middleware.ts, which defaulted to the Edge runtime. Check the current Next.js documentation for the explicit Edge-runtime opt-in if you need to preserve that behavior for a specific file.
How do I migrate middleware.ts to proxy.ts?
- Rename the file —
middleware.tsbecomesproxy.ts - Change the export — the named
export function middlewarebecomesexport default function proxy - Leave imports and config alone —
NextRequest,NextResponse, and thematcherconfig export stay the same - Test locally — run
next devand exercise every path covered by your matcher before deploying
Will my existing middleware.ts file break after upgrading to Next.js 16?
No. Upgrading Next.js to version 16 does not require any changes to an existing middleware.ts file. It continues to run exactly as before. The rename only becomes relevant when you choose to create a proxy.ts file or explicitly migrate the existing one.
Can I still use Edge runtime with proxy.ts?
The default runtime for proxy.ts is Node.js, but Edge runtime hasn't disappeared from Next.js entirely, it's just no longer the default for this specific file. If your use case genuinely depends on Edge-runtime characteristics, check the current Next.js documentation for the explicit runtime configuration, or keep the logic on middleware.ts during the deprecation window while that behavior is fully preserved.
middleware.ts vs proxy.ts: what's the actual difference?
| middleware.ts | proxy.ts | |
|---|---|---|
| Export style | Named export (middleware) | Default export (proxy) |
| Default runtime | Edge | Node.js |
| Status in Next.js 16 | Deprecated, still works | Current default |
| Node.js API access | Restricted | Full |
Use proxy.ts for anything new. Keep an existing middleware.ts file as-is if it depends on Edge-specific behavior you haven't tested under Node.js yet.
Related Articles
How to Use Environment Variables in Next.js (Without Leaking Them to the Browser)
Learn how to use .env files in Next.js correctly. Understand NEXT_PUBLIC_, avoid common mistakes, and set variables in Vercel and Cloudflare.
Migrate Next.js to TanStack Start: Practical Guide with Real Examples
How to migrate a Next.js app to TanStack Start: the two-PR strategy, route migration, replacing next/image, and setting up Nitro, with real production lessons from Railway and Inngest.