LearnFrenzy
Sign in Take a free test

Proxy and Authentication · Lesson 20 of 24

Next.js Proxy (Middleware): Redirects and Rewrites

5 min read Updated Nov 8, 2026 Saurabh Samir

A proxy.ts file at the root of your project runs before a request reaches any page or route handler. It can redirect, rewrite the URL to another page, add headers or cookies, or answer the request directly. In Next.js 16 this feature was renamed from Middleware to Proxy – it works the same way, and you'll still see middleware.ts in older projects.

In this lesson

  • Writing proxy.ts: redirects and rewrites
  • Reading cookies and setting headers
  • Choosing routes with matcher
  • What Proxy should and shouldn't do

Redirects and rewrites

// proxy.ts
// lives at the project root, next to app/
import { NextResponse, type NextRequest } from "next/server";

export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 1. Redirect: old URLs → new URLs (the browser's address bar changes)
  if (pathname.startsWith("/blog/")) {
    return NextResponse.redirect(new URL(pathname.replace("/blog/", "/articles/"), request.url), 308);
  }

  // 2. Rewrite: show a different page without changing the URL
  if (pathname === "/offers" && request.cookies.get("plan")?.value !== "pro") {
    return NextResponse.rewrite(new URL("/offers/upgrade", request.url));
  }

  return NextResponse.next();                      // continue as normal
}

export const config = {
  matcher: ["/blog/:path*", "/offers"],             // only run for these paths
};
// app/articles/[slug]/page.tsx
export function generateStaticParams() {
  return [{ slug: "nextjs-16" }];
}

export default async function Article({ params }: PageProps<"/articles/[slug]">) {
  const { slug } = await params;
  return <h1>Article: {slug}</h1>;
}
// app/offers/page.tsx
export default function Offers() {
  return <h1>Pro member offers: 50% off mentoring</h1>;
}
// app/offers/upgrade/page.tsx
export default function Upgrade() {
  return <h1>Upgrade to Pro to unlock offers</h1>;
}

HTTP response and visits

GET /blog/nextjs-16
→ 308 (redirect to /articles/nextjs-16) 
/articles/nextjs-16

URL: /articles/nextjs-16
Article: nextjs-16

› open /offers
URL: /offers
Upgrade to Pro to unlock offers
RedirectRewrite
URL in the address barchangesstays the same
Extra round tripyes (a 3xx response)no
Typical usesmoved pages, login redirects, forcing a locale prefixA/B tests, paywalls, multi-tenant sites, feature flags

For a fixed list of moved URLs, the redirects() option in next.config.ts is simpler. Use Proxy when the decision depends on the request – cookies, headers, the user's country or device.

Headers and cookies

// proxy.ts
import { NextResponse, type NextRequest } from "next/server";

export function proxy(request: NextRequest) {
  const response = NextResponse.next();
  response.headers.set("x-frame-options", "DENY");                         // a response header
  if (!request.cookies.has("visitor")) {
    response.cookies.set("visitor", crypto.randomUUID(), { httpOnly: true, sameSite: "lax", maxAge: 60 * 60 * 24 * 365 });
  }
  return response;
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],              // everything except static assets
};
// app/page.tsx
import { Suspense } from "react";
import { cookies } from "next/headers";

async function Visitor() {
  const id = (await cookies()).get("visitor")?.value;
  return <p>Visitor cookie: {id ? `present (${id.length} characters)` : "missing"}</p>;
}

export default function Home() {
  return (
    <Suspense fallback={<p>…</p>}>
      <Visitor />
    </Suspense>
  );
}

Two visits

Visitor cookie: present (36 characters)

› reload the page
Visitor cookie: present (36 characters)

Notice the cookie is already "present" on the very first visit. Cookies that Proxy sets on the response are also made visible to Server Components handling the same request, and the browser stores them for every later one.

The matcher

Without a config.matcher, Proxy runs on every request, including JavaScript chunks and images. Matchers accept path patterns (/dashboard/:path*), arrays, and regular expressions like the one above that excludes static files. Matcher values must be constants – they're read at build time.

What Proxy is (and isn't) for

  • Good: redirects and rewrites, locale detection, A/B test bucketing, security headers, bot filtering, quick "is there a session cookie?" checks.
  • Avoid: slow work such as database queries – Proxy runs before every matched request, including prefetches. Keep it fast.
  • Not your security boundary: Server Actions are POST requests to the page that uses them, and a matcher change can quietly stop covering them. Always check authentication and permissions again where the data is read or changed (next lesson).

Proxy uses the Node.js runtime by default, so most npm packages work in it.

Practice

Try it yourself

  1. Redirect /docs (and anything under it) to /learning/nextjs with a permanent 308 status.

    Show solution
    // proxy.ts
    import { NextResponse, type NextRequest } from "next/server";
    
    export function proxy(request: NextRequest) {
      return NextResponse.redirect(new URL("/learning/nextjs", request.url), 308);
    }
    
    export const config = { matcher: ["/docs", "/docs/:path*"] };
    // app/learning/nextjs/page.tsx
    export default function Course() {
      return <h1>Next.js course</h1>;
    }

    HTTP request

    GET /docs/routing
    → 308 (redirect to /learning/nextjs) 
    /learning/nextjs

Quick check

What was Proxy called before Next.js 16?

Middleware (middleware.ts).

Does a rewrite change the URL in the address bar?

No – a redirect does; a rewrite serves another route at the same URL.

Is a session check in Proxy enough to protect your data?

No – verify the session again in the data layer, Server Actions and Route Handlers.

Summary

  • proxy.ts runs before matched requests: redirect, rewrite, set headers and cookies, or respond.
  • Limit it with a constant matcher, and keep it fast.
  • Use it for routing decisions and optimistic checks – never as the only security check.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links