Next.js Middleware Is Now Proxy: proxy.ts Explained
In Next.js 16, middleware.ts was renamed to proxy.ts and now runs on Node.js by default. What proxy does, the matcher config, common uses (auth redirects, rewrites, headers, CORS), the codemod to migrate, and why you must still check authorization inside every Server Function and route.
If you're searching for "Next.js middleware", the first thing to know: as of Next.js 16, the middleware file convention is deprecated and renamed to proxy. Same idea, new name — and a deliberate signal from the Next.js team that it's meant as a narrow, last-resort tool. (What is Next.js?)
What proxy does
proxy.ts (at the project root, or in src/) runs before a request reaches your routes. Based on the request, it can:
- redirect (send logged-out users to
/login) - rewrite (serve a different route without changing the URL)
- set request or response headers and cookies
- respond directly (a 401 for an API call)
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export 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))
}
}
export const config = {
matcher: ['/dashboard/:path*'],
}
The file exports one function — named proxy or as the default export — plus an optional config.
Why the rename
The Next.js docs give two reasons: "middleware" was constantly confused with Express middleware (a chain of request handlers inside your app), and its power encouraged overuse. "Proxy" describes what it actually is: a layer in front of your app, like a reverse proxy. (Reverse proxies explained)
Node.js by default
Proxy now defaults to the Node.js runtime (middleware used to be Edge-only, which ruled out many libraries). Setting a runtime option in the proxy file throws an error.
The matcher: always set one
Without a matcher, proxy runs on every request — including static files, _next/static, image optimisation and public/ assets. An auth redirect without a matcher can block your own CSS and JavaScript.
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
Matchers must be constants (they're analysed at build time). They also support has / missing conditions on headers, cookies and query parameters.
Good uses
- Coarse auth gating: redirect obviously logged-out users away from app pages.
- Redirects and rewrites that depend on the request (locale, A/B bucket, legacy URLs). (301 vs 302)
- Headers: request IDs, security headers. (HTTP security headers)
- CORS for API routes. (CORS errors explained)
Don't rely on it for security
This is the most important point. The Next.js docs are explicit: verify authentication and authorization inside each Server Function and route handler, not only in proxy.
Why:
- Server Functions (Server Actions) are POST requests to the page that uses them. A matcher change, or moving a function to another route, silently removes proxy coverage.
- Proxy checks are usually shallow ("is there a session cookie?"), not "may this user edit this invoice?". (IDOR explained)
Treat proxy as a convenience for user experience, and put real access checks next to the data. (Next.js Server Actions, Authentication vs authorization)
Keep it light
Proxy runs on many requests; slow work there slows everything. Avoid database calls where you can (reading a signed cookie or JWT is fine), and don't rely on shared module state. For background work like logging, event.waitUntil() lets the response go first.
Migrating from middleware.ts
npx @next/codemod@canary middleware-to-proxy .
It renames the file and the exported function:
- export function middleware(request) {
+ export function proxy(request) {
Related config flags were renamed too (skipMiddlewareUrlNormalize → skipProxyUrlNormalize).
Self-hosting
Proxy works on a Node.js server and in Docker; it doesn't work with static export. (Self-hosting Next.js without Vercel)
EasySpawn runs your Next.js app as a Node.js server on your own machine, where proxy, Server Actions and everything else behave exactly as in the docs — and Claude Code can run the codemod for you. See how it works or join the waitlist.
Related: Next.js Server Actions · Self-Hosting Next.js Without Vercel · Next.js App Router vs Pages Router · What Is Next.js?
Keep reading
TanStack Query vs useEffect for Data Fetching in React
Fetching in useEffect looks simple until you need loading states, errors, caching, race conditions, refetching and mutations. What TanStack Query handles for you, side-by-side code, mutations with invalidation, and when server components or plain useEffect are still the right choice.
React State Management: useState vs Context vs Zustand vs Redux
Most React apps need less state management than they think. Sort your state into server, URL, form, local and global, then pick the lightest tool for each: useState, the URL, React Context, Zustand, Redux Toolkit or Jotai. Trade-offs, examples and common mistakes.