Middleware, Proxy, NextRequest & NextResponse
For years, Next.js used a root middleware.ts (or src/middleware.ts) to run code before a request finishes routing — auth checks, redirects, rewrites, headers, A/B cookies.
middleware.ts and the move toward proxy
For years, Next.js used a root middleware.ts (or src/middleware.ts) to run code before a request finishes routing — auth checks, redirects, rewrites, headers, A/B cookies.
Next.js is evolving the naming and mental model toward proxy (proxy.ts) — same idea: intercept the request at the edge of your app, decide, then continue. Docs and versions may show middleware, proxy, or both. Learn the concept once; follow your installed Next.js docs for the exact filename.
Real-life example: Middleware / proxy is the security desk at a company gate. Every visitor passes the desk before entering an office (page).
- Runs before layouts and pages for matched paths
- Great for auth gates, geo redirects, feature flags
- Keep it fast — no heavy database work if you can avoid it
- Check your Next.js version: middleware.ts vs proxy.ts naming
NextRequest and NextResponse
The handler receives a NextRequest (URL, cookies, headers, geo hints). You return a NextResponse — next() to continue, redirect() to send elsewhere, or rewrite() to show another path while keeping the URL.
Real-life example: NextRequest is the visitor badge. NextResponse is the desk officer saying "go in", "go to reception", or "use the side entrance but keep your appointment card".
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const token = request.cookies.get("session")?.value;
if (pathname.startsWith("/dashboard") && !token) {
const login = new URL("/login", request.url);
login.searchParams.set("from", pathname);
return NextResponse.redirect(login);
}
// Continue to the page
const response = NextResponse.next();
response.headers.set("x-rishtaara-path", pathname);
return response;
}
// Only run on matching paths (keeps middleware lean)
export const config = {
matcher: ["/dashboard/:path*", "/account/:path*"],
};import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const country = request.headers.get("x-vercel-ip-country") ?? "US";
if (request.nextUrl.pathname === "/" && country === "IN") {
// Browser still shows "/" but serves /in content
return NextResponse.rewrite(new URL("/in", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/"],
};Protecting routes — redirect vs rewrite
Redirect changes the browser URL — user sees /login. Rewrite keeps the URL but serves another route — useful for localization or maintenance pages without exposing internal paths.
For auth, prefer redirect to /login so the address bar matches reality. Store the original path in a query (?from=) so you can send them back after login.
Real-life example: Redirect is telling someone "go to the ticket counter" and walking them there. Rewrite is seating them in hall B while their ticket still says hall A.
# Typical matcher ideas:
# /dashboard/:path* — dashboard and nested
# /((?!_next|api|favicon.ico).*) — exclude static/api (advanced)
# Keep matcher narrow. Running on every image request wastes edge time.