On this page
Middleware
Middleware runs before a request reaches your pages or API routes. Use it for authentication, redirects, geolocation, and header manipulation.
Creating Middleware
Create middleware.ts at the project root (same level as app/):
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
console.log('Path:', request.nextUrl.pathname);
return NextResponse.next();
}
export const config = {
matcher: '/dashboard/:path*',
};
The matcher config controls which routes trigger middleware. Without it, middleware runs on every request.
Redirects
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/old-page') {
return NextResponse.redirect(new URL('/new-page', request.url));
}
return NextResponse.next();
}
Authentication Guard
Protect routes by checking for a session cookie or token:
export function middleware(request: NextRequest) {
const token = request.cookies.get('session-token');
const isAuthPage = request.nextUrl.pathname.startsWith('/login');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('callbackUrl', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
if (token && isAuthPage) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login'],
};
Rewrites and Headers
Serve a different URL internally without changing the browser address:
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/v2')) {
const url = request.nextUrl.clone();
url.pathname = url.pathname.replace('/api/v2', '/api/v1');
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
Add security headers:
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
return response;
}
Matcher Patterns
export const config = {
matcher: [
'/dashboard/:path*',
'/admin/:path*',
'/((?!_next/static|_next/image|favicon.ico|api).*)',
],
};
| Pattern | Matches |
|---|---|
/dashboard/:path* |
/dashboard, /dashboard/settings |
/api/:path* |
All API routes |
Geolocation and A/B Testing
Access geo data on Vercel:
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US';
if (country === 'DE') {
return NextResponse.redirect(new URL('/de', request.url));
}
return NextResponse.next();
}
Limitations
- Runs on the Edge Runtime — no Node.js APIs (
fs, native database drivers) - Keep logic fast — middleware blocks every matched request
- For heavy auth logic, verify tokens in middleware and load full session in Server Components
Next: style your app with CSS Modules and Tailwind CSS.