Proxy (formerly Middleware)

Next.js · Advanced · 11 min

Request-level logic such as locale redirects, A/B tests and optimistic auth gates belongs in Proxy.

Proxy runs before a request reaches your routes. Since Next.js 16, the feature formerly called Middleware is named Proxy: create proxy.ts in the project root (or src/) and export a function named proxy (or a default export). Only one Proxy file is supported, but it can import modules.

The function receives a NextRequest and can:

  • NextResponse.redirect(new URL('/login', request.url)),
  • NextResponse.rewrite(url) to serve a different route while keeping the URL (A/B tests, multi-tenant hosts),
  • NextResponse.next() to continue, optionally setting request or response headers and cookies,
  • return a Response directly.

Limit where it runs with export const config = { matcher: [...] }. Exclude static assets and images, or every file request pays the cost.

Proxy uses the Node.js runtime by default in Next.js 16 (the runtime option isn't allowed in Proxy files). Keep it fast: it runs on every matched request.

Good uses: redirects based on cookies or headers, locale detection, rewrites, adding security headers, and optimistic auth checks (is there a session cookie at all?). Not good: slow data fetching or being your only authorisation layer. Verify sessions again close to the data, in the data access layer or each action. For static redirects, prefer redirects() in next.config.ts.

Example

// proxy.ts (project root)
import { NextResponse, type NextRequest } from 'next/server'

const protectedPrefixes = ['/dashboard', '/settings']

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl
  const hasSession = request.cookies.has('session')

  // optimistic check only; real authorization happens next to the data
  if (protectedPrefixes.some((p) => pathname.startsWith(p)) && !hasSession) {
    const login = new URL('/login', request.url)
    login.searchParams.set('next', pathname)
    return NextResponse.redirect(login)
  }

  const response = NextResponse.next()
  response.headers.set('X-Frame-Options', 'DENY')
  return response
}

export const config = {
  // skip API routes, static files and images
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

Remember

  • proxy.ts (formerly middleware.ts) runs before matched requests
  • Redirect, rewrite, set headers or respond; scope it with config.matcher
  • Runs on Node.js by default; keep it fast and use it for optimistic checks only

Watch out for

  • Doing the only authorisation check in Proxy; Server Actions and Route Handlers must verify access themselves.

Check yourself

  1. What is the main difference between NextResponse.rewrite and NextResponse.redirect?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nextjs.org/docs/app/getting-started/proxy