- Published on
Your middleware.ts Is Being Silently Ignored in Next.js 16
- Authors

- Name
- Salman
- X
- @lamebrowndev

The silent break
You upgraded to Next.js 16. The build passed. The dev server started. Everything looked fine. Then you noticed your auth redirects stopped working. Users who should be sent to the login page are landing on protected routes. Your middleware is just... not running.
You check the file. It is right there. middleware.ts sitting in the root of your project where it has always been. The code looks correct. The matcher config is fine. There are no errors in the console because there is nothing to error on. The file is simply being ignored.
Here is what happened. Next.js 16 renamed middleware.ts to proxy.ts. The old filename still exists for backwards compatibility in some cases but the framework now looks for proxy.ts first. Depending on your setup the old file might get skipped entirely. No warning, no error, no deprecation message. Just silence.
I have gotten three messages about this in the last week from people who upgraded and lost their middleware without realizing it. One of them had an admin panel exposed for two days before a user reported it. That is the kind of thing that keeps you up at night.
Why the rename
The Next.js team had a good reason for this even if the execution was rough. The word "middleware" implies something specific in web frameworks. In Express, Koa, and most Node frameworks middleware is a chain of functions that run in order and each one can modify the request or response. Next.js middleware was never really that. It was a single function that ran before your routes and could redirect or rewrite requests.
With Next.js 16 the team wanted to align the name with what the file actually does. It acts as a proxy layer between the client and your routes. You can rewrite URLs, add headers, redirect based on geography, and do auth checks. "Proxy" is a more honest name for that.
The new proxy.ts also supports a slightly different API that lets you define multiple handlers instead of one giant function. But the core behavior is the same. It runs before your routes and can modify the request.
How to fix it
Step 1: Rename the file
mv middleware.ts proxy.ts
That is the main fix. If your file was called middleware.ts in the project root (same level as package.json or inside src/), rename it to proxy.ts.
If you had middleware.js or middleware.tsx, the same applies. Rename to proxy.js or proxy.tsx.
Step 2: Update the exports if needed
The basic export stays the same. If you had this:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('session-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
You can keep it almost identical. Just rename the function:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const token = request.cookies.get('session-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
Wait, actually the exported function name does not matter. The framework looks for a default export or a named export called proxy or middleware. For backwards compatibility the old name middleware still works as an export name. But to be safe and future-proof, rename the function to proxy.
Actually let me be more precise. In Next.js 16 the file proxy.ts is detected by filename. The function inside can be called anything as long as it is the default export or matches the expected names. The safest approach:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export default function handler(request: NextRequest) {
const token = request.cookies.get('session-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
Step 3: Check for imports of middleware
If any part of your code imports from the middleware file, update those paths:
// Before
import { middleware } from '@/middleware'
// After
import { handler } from '@/proxy'
This is rare but I have seen it in monorepos where shared utilities reference the middleware function.
Step 4: Update your CI and scripts
If your CI pipeline or any script references the filename directly, update those too. This includes:
- Dockerfiles that COPY specific files
- ESLint configs with path-specific rules
- Test files that mock or import middleware
- Documentation that references the filename
The new proxy API
Next.js 16 also introduced a way to define multiple proxy handlers instead of one function with a big if/else chain. This is useful if you have different concerns like auth, geo-redirects, and header injection:
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// Auth handler
export const proxy = [
{
condition: (request: NextRequest) => {
return request.nextUrl.pathname.startsWith('/admin')
},
handler: (request: NextRequest) => {
const token = request.cookies.get('session-token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
},
{
condition: (request: NextRequest) => {
return request.nextUrl.pathname.startsWith('/eu')
},
handler: (request: NextRequest) => {
// Geo redirect logic
const country = request.geo?.country
if (country && country !== 'GB') {
return NextResponse.redirect(new URL('/us', request.url))
}
}
}
]
export const config = {
matcher: ['/admin/:path*', '/eu/:path*']
}
You do not have to use this. The single function approach still works fine. But if your middleware was becoming a 200-line mess of conditionals, the array approach is cleaner.
What if both files exist
If you have both middleware.ts and proxy.ts in your project, Next.js 16 will use proxy.ts and ignore middleware.ts silently. This is dangerous because you might have updated one and not the other.
If you are in this situation, delete the old file:
rm middleware.ts
git add -A && git commit -m "remove old middleware.ts after proxy.ts rename"
Do not keep both. You will forget which one is active and debug the wrong file.
A checklist to make sure nothing else broke
After renaming, go through this quickly:
- Does your proxy file export a function (default or named)?
- Does the
config.matcherstill cover the paths you need? - Are there any other files in your project that import from the old filename?
- Does your Docker build or deployment script reference
middleware.tsby name? - Do you have any tests that mock or spy on the middleware function?
- Is there a
middleware.spec.tsormiddleware.test.tsthat needs renaming?
If all of those check out you are good. Your middleware, sorry, your proxy is running again.
This is the second time Next.js has done a silent rename that broke auth. The pages/api to app/api migration had similar issues where old route handlers were ignored without warning. The team needs to start printing a big warning when a known filename is present but deprecated. A console message that says "middleware.ts is deprecated, rename to proxy.ts" would have saved a lot of people from shipping unprotected routes.
For now, just rename your file and move on. It is a one-line fix but it is a one-line fix that should have come with a one-line warning.
I fix Next.js production issues for a living. If your middleware stopped working after the Next.js 16 upgrade and you want someone to audit what else might have silently broken, email me.
