Next.js Middleware
Answer
Middleware runs before a request is completed, allowing you to modify the response by rewriting, redirecting, modifying headers, or returning directly.
How Middleware Works
Creating Middleware
// middleware.ts (root of project)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Runs on every matched request
console.log("Request to:", request.nextUrl.pathname);
return NextResponse.next();
}
// Configure which paths trigger middleware
export const config = {
matcher: "/api/:path*",
};
Common Use Cases
// 1. Authentication
export function middleware(request: NextRequest) {
const token = request.cookies.get("token")?.value;
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
// 2. Redirects
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === "/old-page") {
return NextResponse.redirect(new URL("/new-page", request.url));
}
}
// 3. Rewrites (URL stays same, content from different path)
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === "/api/v1/users") {
return NextResponse.rewrite(new URL("/api/v2/users", request.url));
}
}
// 4. Adding headers
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set("x-custom-header", "my-value");
return response;
}
Matcher Configuration
// Match single path
export const config = {
matcher: "/dashboard",
};
// Match multiple paths
export const config = {
matcher: ["/dashboard/:path*", "/api/:path*"],
};
// Match with regex
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
// Skip static files and images
export const config = {
matcher: ["/((?!api|_next/static|_next/image|.*\\.png$).*)"],
};
Geolocation & IP
export function middleware(request: NextRequest) {
const country = request.geo?.country || "US";
const city = request.geo?.city || "Unknown";
const ip = request.ip || "unknown";
// Redirect based on location
if (country === "DE") {
return NextResponse.redirect(new URL("/de", request.url));
}
// Add location to headers for use in pages
const response = NextResponse.next();
response.headers.set("x-user-country", country);
return response;
}
Reading & Setting Cookies
export function middleware(request: NextRequest) {
// Read cookie
const theme = request.cookies.get("theme")?.value || "light";
const response = NextResponse.next();
// Set cookie
response.cookies.set("visited", "true", {
httpOnly: true,
secure: true,
maxAge: 60 * 60 * 24, // 1 day
});
// Delete cookie
response.cookies.delete("old-cookie");
return response;
}
Chain of Checks
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// 1. Check maintenance mode
if (process.env.MAINTENANCE_MODE === "true") {
return NextResponse.rewrite(new URL("/maintenance", request.url));
}
// 2. Check authentication for protected routes
const isProtected =
pathname.startsWith("/dashboard") || pathname.startsWith("/settings");
const token = request.cookies.get("auth-token");
if (isProtected && !token) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("redirect", pathname);
return NextResponse.redirect(loginUrl);
}
// 3. Add security headers
const response = NextResponse.next();
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
return response;
}
Rate Limiting Example
const rateLimit = new Map();
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api")) {
const ip = request.ip || "anonymous";
const count = rateLimit.get(ip) || 0;
if (count > 100) {
return NextResponse.json({ error: "Too many requests" }, { status: 429 });
}
rateLimit.set(ip, count + 1);
setTimeout(() => rateLimit.delete(ip), 60000);
}
return NextResponse.next();
}
Key Points
middleware.tsat project root runs before requests- Use
matcherto limit which paths run middleware - Can redirect, rewrite, set headers, or block
- Access cookies, headers, geo, and IP
- Runs at the Edge (fast, but limited APIs)
- Use for auth, redirects, A/B testing, headers