Lesson 32 of 81
Same file, same job, two different names depending on when the code you're reading was written — and knowing which era a snippet comes from is half of debugging it.
10 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Before Next.js 16, the request-interception file at the project root was called middleware.ts, and it exported a function named middleware. As of 16.0.0, both names are deprecated in favor of proxy.ts and an exported proxy function. This lesson isn't about what that file does — the mechanics of matchers, redirects, and execution order are their own lesson. This one is about the rename itself: why it happened, what didn't change underneath it, and why you'll keep running into the old name for a while yet.
The motivation was a genuine, repeated source of confusion. "Middleware" already means something specific in the wider JavaScript ecosystem: an Express or Koa handler that sits inside the request/response chain, one of several functions each calling next() to pass control to the one after it. Next.js's file never worked that way — there's no chain, no next(), just one function deciding whether a request proceeds, gets redirected, gets rewritten, or gets answered directly, all before anything renders. "Proxy" describes that behavior more literally: it's a gatekeeper standing in front of your app, not a link in an internal handler chain.
// Next.js 15 and earlier — middleware.ts
import { type NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const isLoggedIn = request.cookies.has("session");
if (!isLoggedIn) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
export const config = { matcher: "/dashboard/:path*" };
// Next.js 16+ — proxy.ts
import { type NextRequest, NextResponse } from "next/server";
export function proxy(request: NextRequest) {
const isLoggedIn = request.cookies.has("session");
if (!isLoggedIn) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
export const config = { matcher: "/dashboard/:path*" };
Line for line, these are the same logic. NextRequest, NextResponse, config.matcher — none of it moved. Only the filename and the exported function's name changed. If you're reading a tutorial, a Stack Overflow answer, or an older open-source repo and it says middleware.ts, don't assume the code is wrong for your version — assume it predates the rename, and mentally substitute the new names before you judge whether the logic itself still applies.
middleware.ts if they haven't republished for v16 yet — that's a documentation staleness issue on their end, not a sign your installed Next.js version is wrong. Check your own node_modules/next version and its docs, not a vendor's example snippet, as the source of truth.proxy.ts."I see middleware.ts in this codebase but the docs I'm reading mention proxy.ts — which one is current?" is exactly the kind of question that separates someone who's kept their mental model in sync with the framework from someone pattern-matching against whatever tutorial they last read. The right answer names the rename, the version it landed in, and — more importantly — that the underlying request-interception mechanism (one gatekeeper function, run before rendering, configured via a matcher) never changed at all.