Use Next.js middleware for redirects, auth checks, headers, and request rewriting in the App Router.
middleware.ts file at the root of your project, next to the app directory.// middleware.ts
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
return NextResponse.next();
}matcher export to specify which paths trigger the middleware.export const config = {
matcher: ['/api/:path*', '/admin/:path*']
};NextResponse.next() to continue to the next handler.export function middleware(request: NextRequest) {
const token = request.cookies.get('auth')?.value;
if (!token && request.nextUrl.pathname.startsWith('/admin')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}NextResponse.redirect() when you want that visible change.if (request.nextUrl.pathname === '/old-page') {
return NextResponse.redirect(new URL('/new-page', request.url));
}if (request.nextUrl.pathname.startsWith('/api')) {
return NextResponse.rewrite(new URL('/api-v2/handler', request.url));
}export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('X-Custom-Header', 'value');
return response;
}const userAgent = request.headers.get('user-agent');const locale = request.headers.get('accept-language')?.split(',')[0].split('-')[0];
if (locale === 'es') {
return NextResponse.redirect(new URL('/es' + request.nextUrl.pathname, request.url));
}request.geo and request.ip were removed from NextRequest in Next.js 15 — use the geolocation() helper from @vercel/functions to route based on the user's country instead.import { geolocation } from '@vercel/functions';
export function middleware(request: NextRequest) {
const { country } = geolocation(request);
if (country === 'DE') {
return NextResponse.redirect(new URL('/de', request.url));
}
}ipAddress() from @vercel/functions now that request.ip is gone.import { ipAddress } from '@vercel/functions';
const ip = ipAddress(request) ?? request.headers.get('x-forwarded-for') ?? 'unknown';
// Check rate limit store keyed by ip...Place middleware.ts (or middleware.js) in the root of your project, at the same level as your app or pages directory. Only one middleware file is supported per project.
Export a matcher config from your middleware file to target specific paths, e.g. export const config = { matcher: ['/dashboard/:path*', '/api/protected/:path*'] }. Without a matcher, middleware runs on every single request including static assets.
Read your auth token from cookies or headers using request.cookies.get() or request.headers.get(), then return NextResponse.redirect(new URL('/login', request.url)) if the check fails.
Yes — use NextResponse.next() and call response.headers.set('X-Custom-Header', 'value') before returning it. This is useful for adding security headers like Content-Security-Policy or X-Frame-Options globally.
A redirect sends the browser to a new URL (visible in the address bar), while a rewrite proxies the request to a different path internally without changing the URL the user sees. Use NextResponse.rewrite() for URL masking and NextResponse.redirect() when you want the client to navigate.