Next.js 16 replaced middleware.ts with proxy.ts
In short
In Next.js 16, middleware.ts is gone. The replacement is proxy.ts at the project root, exporting a function named proxy(request) and a config.matcher, and it runs on the Node runtime rather than Edge. The matcher is read statically at build time, so it cannot reference an imported constant.

- Old
- middleware.ts, export default middleware(), Edge runtime
- New
- proxy.ts, export function proxy(request), Node runtime
- Export name
- Must be `proxy` — a default export is not picked up
- config.matcher
- Read statically at build; cannot be a computed value
If you are upgrading to Next.js 16 and your middleware.ts has quietly stopped running, it is not broken — it is no longer the file Next looks for. The concept survives under a new name, a new export, and a different runtime.
#What actually changed
| Before | Next.js 16 | |
|---|---|---|
| File | middleware.ts | proxy.ts |
| Export | default, or named middleware | named proxy |
| Signature | (request: NextRequest) | (request: NextRequest) |
| Runtime | Edge | Node |
| Config | export const config | export const config |
The rename is the visible part. The runtime move is the part that changes what you can write: on Node you are no longer confined to the Edge subset, so Node built-ins and libraries that depend on them work in a file where they previously could not.
#The shape of the new file
import { NextResponse, type NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const res = NextResponse.next();
res.headers.set("X-Robots-Tag", "noindex, nofollow, noarchive");
return res;
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};#The matcher is read statically
This is the one that cost me real time. config.matcher is not evaluated at runtime — Next reads it out of the source at build time, before any module graph exists. That means it cannot reference anything imported.
// ✗ Does not work. ADMIN_PATH is an import; the build-time reader
// cannot resolve it, and the matcher ends up wrong or empty.
import { ADMIN_PATH } from "@/lib/admin-path";
export const config = { matcher: [`${ADMIN_PATH}/:path*`] };
// ✓ Works. The literal has to be present in this file.
export const config = { matcher: ["/desk-4f7a/:path*"] };If you keep a path constant in a shared module — which you should, if several files need it — the matcher becomes a deliberate duplication of that value. Comment it as such at both ends, because it looks exactly like the kind of duplication a future reader will helpfully refactor away.
#Migrating in practice
- Rename
middleware.tstoproxy.ts, keeping it at the project root. - Rename the exported function to
proxyand make sure it is a named export. - Inline any imported constants used inside
config.matcheras string literals. - Re-check anything that assumed the Edge runtime — the file now runs on Node.
- Verify against a production build, not
next dev.
#Why the rename is the right call
"Middleware" was borrowed from Express, where it means a chain of handlers each calling the next. Next's version was never that. It was one function, running once, in front of routing — closer to a reverse proxy than to a middleware stack, and the name led people to expect composition that was never there.
proxy describes what the file actually is. Renames are annoying exactly once; a misleading name is annoying every time somebody new reads the code.
Questions this answers
What replaced middleware.ts in Next.js 16?
proxy.ts, placed at the project root. It exports a named function `proxy(request)` instead of a default or `middleware` export, and it runs on the Node runtime rather than the Edge runtime. The `config.matcher` export works the same way.
Why is my Next.js 16 proxy.ts not running?
The most common causes are a default export instead of the required named `proxy` export, or a config.matcher that references an imported constant. The matcher is read statically at build time, so it can only contain string literals present in that file. Neither mistake produces an error.
Can config.matcher use a variable in Next.js 16?
No. Next reads config.matcher out of the source at build time, before the module graph is resolved, so imported constants and computed values cannot be used. The matcher patterns must be string literals written directly in proxy.ts.
See also
- Learnings — The skill map — knowledge domains, competencies and credentials



