Proxy (Middleware): Redirects, Rewrites and Request Interception

Intermediate
12 min

Proxy (Middleware): Redirects, Rewrites and Request Interception

Some decisions must be made before a request reaches any page or handler: redirect an old URL, choose a locale, block a bot, or attach a header for downstream code. Next.js runs a single proxy.ts file for this purpose; it was called middleware.ts until Next.js 16. After this lesson you will be able to write a proxy, scope it with a matcher, redirect and rewrite requests, and know where its limits are.

Creating proxy.ts

Place proxy.ts at the project root, or inside src/ if you use that layout, and export a function named proxy:

typescript
// proxy.ts import { NextResponse, type NextRequest } from "next/server"; export function proxy(request: NextRequest) { return NextResponse.next(); // continue to the matched route }

The function runs on the Node.js runtime for every matched request and must return a response: NextResponse.next() to continue, or a redirect, rewrite or custom response to short-circuit. Only one proxy file is allowed per project; branch on request.nextUrl.pathname inside it.

When upgrading an older project, npx @next/codemod@latest middleware-to-proxy . renames the file and the export for you.

Scoping With matcher

Without a matcher the proxy runs on every request, including static assets. Restrict it:

typescript
export const config = { matcher: [ "/dashboard/:path*", // a section and everything beneath it "/((?!api|_next/static|_next/image|favicon.ico).*)", // everything except these ], };

| Pattern | Matches | |---|---| | /about | Exactly /about | | /blog/:slug | One segment: /blog/hello | | /docs/:path* | Zero or more segments under /docs | | Regex in parentheses | Anything the expression allows, as in the negative lookahead above |

Matcher values must be static strings so Next.js can analyse them at build time.

Redirects and Rewrites

A redirect changes the URL in the browser and returns 307 or 308. A rewrite serves a different route while the address bar stays the same:

typescript
export function proxy(request: NextRequest) { const url = request.nextUrl; // Permanent redirect for a moved page if (url.pathname === "/old-pricing") { return NextResponse.redirect(new URL("/pricing", request.url), 308); } // Rewrite: show /beta/home to flagged users without changing the URL if (url.pathname === "/" && request.cookies.get("beta")?.value === "1") { return NextResponse.rewrite(new URL("/beta/home", request.url)); } return NextResponse.next(); }

Rewrites power A/B tests, locale prefixes and multi-tenant routing (mapping acme.example.com to /tenants/acme). Redirects that never change belong in the redirects() option of next.config.ts instead.

Modifying Headers and Cookies

The proxy can attach request headers that Server Components and handlers later read, and set response headers or cookies:

typescript
export function proxy(request: NextRequest) { const requestHeaders = new Headers(request.headers); requestHeaders.set("x-request-id", crypto.randomUUID()); const response = NextResponse.next({ request: { headers: requestHeaders } }); response.headers.set("x-frame-options", "DENY"); response.cookies.set("visited", "1", { path: "/" }); return response; }

Downstream code reads the id with (await headers()).get("x-request-id"), a simple way to correlate logs.

What the Proxy Is Not For

  • Sole authentication check. A past vulnerability allowed crafted headers to skip middleware entirely. Use the proxy for optimistic redirects, but verify sessions again in the data layer.
  • Database queries or slow work. It runs on every matched request; keep it to cookie reads, header checks and URL logic.
  • Rendering. It cannot return React components; rewrite to a route instead.
Quick Quiz
Question 1 of 3

What is the name of the file that intercepts requests in Next.js 16?

Key Takeaways

  • proxy.ts (formerly middleware.ts) runs before routing for every request matched by config.matcher.
  • Return NextResponse.next(), a redirect, a rewrite or a custom response.
  • Redirects change the URL; rewrites serve another route silently, enabling A/B tests and tenant routing.
  • Pass modified request headers through NextResponse.next({ request: { headers } }).
  • Keep it fast and never rely on it as the only authentication check.

Next lesson: Styling: CSS Modules, Tailwind and Global CSS — the built-in ways to style a Next.js application.

Proxy (Middleware): Redirects, Rewrites and Request Interception - Next.js | CodeYourCraft | CodeYourCraft