Blog
3 min read

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