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
Responsedirectly.
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(formerlymiddleware.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.