In production web architectures, intercepting and sanitizing incoming traffic before application servers spend CPU cycles rendering React components or executing heavy database queries is paramount:
x-vercel-ip-country) and redirecting to the matching locale prefix.Next.js addresses these edge gateway responsibilities using Next.js Middleware. This code executes at the distributed Edge Network layer for incoming HTTP requests prior to server cache evaluation and route tree reconciliation.
The middleware.ts file must be declared strictly at the project root (adjacent to app or src).
The defining architectural characteristic of Next.js Middleware is its execution within the Edge Runtime. This is a secure V8 sandbox providing standard Web APIs (fetch, Request, Response, Web Crypto, Headers), but deliberately excluding heavy native Node.js libraries (fs, child_process, raw TCP sockets).
By executing on edge CDN nodes close to the user, Middleware evaluates requests in under 10ms, dropping unauthenticated requests before they ever reach the central origin data center.
Incoming HTTP request is intercepted by the nearest geographically distributed CDN edge node.
The engine checks the request path against config.matcher regex, bypassing static assets and images.
High-speed JWT or session cookie verification via Web Crypto API without blocking database lookups.
Generates cryptographic CSP nonces and appends contextual headers like x-user-id to NextResponse.next().
Resolves route action: proceed (NextResponse.next), redirect (HTTP 307), or internal proxy rewrite (rewrite).
What is the fundamental architectural difference between NextResponse.redirect() and NextResponse.rewrite()?
The middleware.ts file exports a middleware function alongside a config.matcher configuration object:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session_token')?.value;
// Authentication guard for protected application sections
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
// Route matching patterns where Middleware should execute
export const config = {
matcher: [
/*
* Exclude internal assets and static assets for optimal performance:
* - api (unless global middleware is explicitly needed)
* - _next/static (static JS/CSS assets)
* - _next/image (optimized images)
* - favicon.ico, sitemap.xml, robots.txt
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
};Next.js allows enriching incoming requests with downstream headers (such as injected user IDs or CSP nonces):
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Clone incoming request headers
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-gateway-timestamp', Date.now().toString());
requestHeaders.set('x-client-ip', request.ip ?? 'unknown');
// Forward mutated headers to downstream Server Components
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}Why is executing heavy relational database queries via Prisma or pg strongly discouraged inside Next.js Middleware?
A core architectural pattern for multi-tenant SaaS platforms where visitors navigate to company.platform.com while the server internally maps the request to app/tenants/[tenant]/page.tsx:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const hostname = request.headers.get('host') ?? '';
const pathname = request.nextUrl.pathname;
// Extract tenant subdomain (e.g., acme.platform.com -> acme)
const currentHost = hostname.replace(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, '');
// If this is a tenant subdomain, proxy internally
if (currentHost && currentHost !== 'www' && currentHost !== 'localhost:3000') {
return NextResponse.rewrite(
new URL(`/tenants/${currentHost}${pathname}`, request.url)
);
}
return NextResponse.next();
}When redirecting unauthenticated users to /login, failing to exclude /login from the config.matcher or conditional checks causes Middleware to endlessly redirect to itself, crashing with ERR_TOO_MANY_REDIRECTS.
Mitigation: Always verify that the current path is not already the target route: if (!token && !request.nextUrl.pathname.startsWith('/login')).
A frequent mistake is mutating request.headers.set(...) and then returning an empty NextResponse.next(). The mutated headers are discarded and never reach Server Components.
Mitigation: Explicitly pass the request: { headers: requestHeaders } payload inside NextResponse.next().