Next.js middleware runs before every request at the edge — defined in a single middleware.ts file at the project root, it can redirect, rewrite, set headers/cookies, and return responses, using a limited edge runtime without Node.js APIs like fs or native modules.
Runs before any rendering or route handler — intercepts requests to redirect, rewrite, modify headers, or return responses.
Defined in one middleware.ts at the project root. config.matcher controls which routes trigger it — without matcher, runs on every request.
Runs in a lightweight edge environment — Web APIs only, no Node.js (fs, path, native modules). Fast, globally distributed.
Authentication redirects, internationalization routing, A/B testing via rewrites, security headers, and geolocation-based content.
Middleware in Next.js runs before a request is completed — it sits between the incoming request and your routes, allowing you to intercept, modify, or redirect requests globally.
Middleware is defined in a single middleware.ts (or .js) file at the root of your project (next to app/ or pages/). It exports a function that receives a NextRequest and returns a NextResponse:
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
// Check authentication
const token = request.cookies.get('session');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};Middleware runs on every matching request before any rendering or route handler execution.
The config.matcher array specifies which routes trigger the middleware. Without a matcher, middleware runs on every route (including static assets, which you usually don't want). Matchers support path patterns:
/dashboard/:path* — matches /dashboard and all sub-paths/api/:path* — matches all API routes/((?!_next|static|favicon.ico).*) — regex to exclude internal routesNextResponse.redirect(url) — send users to a different page (auth redirects, localization)NextResponse.rewrite(url) — serve a different page without changing the URL (A/B testing, feature flags)response.headers.set('x-custom', 'value') — add security headers, CORSresponse.cookies.set('name', 'value') — session management, preferencesNextResponse directly — rate limiting, blockingMiddleware runs in the edge runtime — a lightweight JavaScript environment optimized for speed and global distribution. It does NOT have access to:
fs, path, crypto (use Web Crypto API instead), child_processThe edge runtime supports Web APIs: fetch, Request/Response, Headers, URL, TextEncoder, crypto.subtle, and ReadableStream.
request.geo (Vercel) to route users to regional contentDon't put heavy computation or data fetching in middleware — it runs on every request and should be fast.
Middleware runs before every matched request at the edge. It can redirect, rewrite, set headers/cookies, and return responses. Defined in a single middleware.ts file with a matcher config. Uses the edge runtime (Web APIs only, no Node.js). Common uses: authentication redirects, i18n, A/B testing, security headers. Keep it lightweight — it runs on every request.
Fun Fact
Next.js middleware was originally envisioned as per-route middleware files (middleware.ts inside each route folder), but the design was changed to a single global middleware file before the stable release. The single-file approach was chosen because middleware needs to run before routing decisions are made — per-route files would require parsing the route first, defeating the purpose.