Middleware runs before every request, giving you complete control to intercept, modify, redirect, or rewrite requests. Use middleware for authentication, logging, redirects,header modification, and any global logic needed across your app. Middleware runs on the Edge runtime for incredible performance. Master middleware and unlock powerful request-level control!
What is Middleware?
Request Flow with Middleware
- User requests a page (e.g., /dashboard)
- Middleware runs first (before anything else)
- Middleware can: redirect, rewrite, modify headers, or continue
- If middleware continues, request proceeds to page/API route
- Page renders and response sent to user
Common Middleware Use Cases
- Authentication: Check if user is logged in before showing protected pages
- Redirects: Redirect users based on conditions (locale, device, etc.)
- Rewrites: Serve different content without changing the URL
- Headers: Add security headers, CORS headers, custom headers
- Logging: Track requests, analytics, debugging
- A/B Testing: Route users to different versions
- Rate Limiting: Prevent abuse
- Bot Detection: Handle bots differently
Basic Middleware Setup
Creating Middleware File
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// This function can be marked 'async' if needed
export function middleware(request: NextRequest) {
console.log('Middleware running for:', request.nextUrl.pathname);
// Continue to the requested page
return NextResponse.next();
}
// Optionally configure which routes middleware runs on
export const config = {
matcher: '/:path*', // Run on all routes
};
// File location: middleware.ts in project root
// OR: src/middleware.ts if using src directory
// ✅ Export middleware function
// ✅ Receives NextRequest
// ✅ Returns NextResponse
// ✅ Optional matcher configurationMiddleware Function Signature
import { NextResponse, NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Request properties
const pathname = request.nextUrl.pathname; // /dashboard
const url = request.url; // Full URL
const method = request.method; // GET, POST, etc.
const headers = request.headers; // Request headers
const cookies = request.cookies; // Request cookies
// Must return NextResponse
return NextResponse.next(); // Continue
// OR
return NextResponse.redirect(new URL('/login', request.url));
// OR
return NextResponse.rewrite(new URL('/other', request.url));
// OR
return new Response('Blocked', { status: 403 });
}
// ✅ NextRequest has cookies, geo, IP, nextUrl
// ✅ Must return a Response
// ✅ NextResponse has helpers (next, redirect, rewrite)Matcher Configuration
Matcher Patterns
// Match all routes
export const config = {
matcher: '/:path*',
};
// Match specific routes
export const config = {
matcher: '/dashboard/:path*',
};
// Match multiple routes
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*'],
};
// Match with conditions (exclude static files)
export const config = {
matcher: [
/*
* Match all request paths except:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
// Exclude specific paths
export const config = {
matcher: [
// Match everything except static files
'/((?!_next/static|_next/image|favicon.ico).*)',
],
};
// ✅ matcher is an array or string
// ✅ Use regex patterns
// ✅ Exclude static assets for performanceAdvanced Matcher Examples
// Only authenticated routes
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'],
};
// Only specific file types
export const config = {
matcher: '/api/:path*.json', // Only JSON API routes
};
// Everything except public routes
export const config = {
matcher: [
'/((?!login|register|forgot-password|_next/static|_next/image).*)',
],
};
// Multiple specific paths
export const config = {
matcher: [
'/dashboard',
'/admin/:path*',
'/api/protected/:path*',
],
};
// ✅ Matcher supports path patterns
// ✅ :path* matches nested routes
// ✅ Negative lookahead (?!...) excludes pathsRedirects with Middleware
Basic Redirect
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Redirect /old-page to /new-page
if (request.nextUrl.pathname === '/old-page') {
return NextResponse.redirect(new URL('/new-page', request.url));
}
return NextResponse.next();
}
// Visit /old-page → Redirects to /new-page
// ✅ User sees /new-page in address bar
// ✅ Browser receives 307/308 redirect
// ✅ Must use absolute URLConditional Redirects
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Check if user is authenticated (simplified)
const isAuthenticated = request.cookies.has('session');
// Redirect unauthenticated users from protected routes
if (pathname.startsWith('/dashboard') && !isAuthenticated) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Redirect authenticated users away from login
if (pathname === '/login' && isAuthenticated) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login'],
};
// ✅ Authentication-based redirects
// ✅ Prevent access to protected routes
// ✅ Redirect logged-in users away from loginRedirect with Query Parameters
export function middleware(request: NextRequest) {
const isAuthenticated = request.cookies.has('session');
if (request.nextUrl.pathname.startsWith('/dashboard') && !isAuthenticated) {
// Create login URL with return path
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
// Visit /dashboard/settings (not logged in)
// → Redirects to /login?returnTo=/dashboard/settings
// ✅ Preserve intended destination
// ✅ Redirect back after login
// ✅ Better user experienceRewrites with Middleware
Basic Rewrite
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Rewrite /blog to /posts
if (request.nextUrl.pathname === '/blog') {
return NextResponse.rewrite(new URL('/posts', request.url));
}
return NextResponse.next();
}
// Visit /blog → Shows content from /posts
// ✅ URL stays /blog in address bar
// ✅ Content served from /posts
// ✅ Invisible to userA/B Testing with Rewrites
export function middleware(request: NextRequest) {
// A/B test - 50% of users see variant
const bucket = request.cookies.get('bucket')?.value;
if (!bucket) {
// Randomly assign bucket
const newBucket = Math.random() < 0.5 ? 'a' : 'b';
const response = NextResponse.next();
response.cookies.set('bucket', newBucket, {
maxAge: 60 * 60 * 24 * 30, // 30 days
});
return response;
}
// Rewrite based on bucket
if (request.nextUrl.pathname === '/') {
if (bucket === 'b') {
return NextResponse.rewrite(new URL('/variant-b', request.url));
}
}
return NextResponse.next();
}
// ✅ 50% see original
// ✅ 50% see variant
// ✅ Consistent per user (cookie)
// ✅ URL unchangedLocale-Based Rewrites
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Get locale from cookie or header
const locale = request.cookies.get('locale')?.value ||
request.headers.get('accept-language')?.split(',')[0] ||
'en';
// Rewrite to locale-specific version
if (pathname === '/') {
return NextResponse.rewrite(new URL(`/${locale}`, request.url));
}
// Add locale header for pages to use
const response = NextResponse.next();
response.headers.set('x-locale', locale);
return response;
}
// Visit / → Serves content from /en (or user's locale)
// ✅ Automatic locale routing
// ✅ URL stays simple
// ✅ Locale in header for pagesModifying Headers
Adding Request Headers
export function middleware(request: NextRequest) {
// Clone request headers
const requestHeaders = new Headers(request.headers);
// Add custom headers
requestHeaders.set('x-user-id', 'user-123');
requestHeaders.set('x-request-time', new Date().toISOString());
// Continue with modified headers
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
});
return response;
}
// Pages/API routes can read these headers:
// const userId = headers().get('x-user-id');
// ✅ Add headers to request
// ✅ Available in pages/routes
// ✅ Pass data to downstream handlersAdding Response Headers
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Security headers
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'origin-when-cross-origin');
response.headers.set(
'Permissions-Policy',
'camera=(), microphone=(), geolocation=()'
);
// CORS headers
response.headers.set('Access-Control-Allow-Origin', '*');
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
// Custom headers
response.headers.set('X-Custom-Header', 'value');
return response;
}
// ✅ Security headers
// ✅ CORS configuration
// ✅ Custom headers
// ✅ Applied to all responsesContent Security Policy
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Content Security Policy
const csp = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self' https://api.example.com",
].join('; ');
response.headers.set('Content-Security-Policy', csp);
return response;
}
// ✅ CSP for security
// ✅ Prevent XSS attacks
// ✅ Control resource loadingWorking with Cookies
Reading Cookies
export function middleware(request: NextRequest) {
// Get single cookie
const session = request.cookies.get('session');
console.log('Session:', session?.value);
// Get all cookies
const allCookies = request.cookies.getAll();
console.log('All cookies:', allCookies);
// Check if cookie exists
const hasSession = request.cookies.has('session');
if (!hasSession) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// ✅ request.cookies.get(name)
// ✅ request.cookies.getAll()
// ✅ request.cookies.has(name)Setting Cookies
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Set cookie
response.cookies.set('visited', 'true', {
maxAge: 60 * 60 * 24 * 365, // 1 year
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
});
// Set multiple cookies
response.cookies.set('theme', 'dark');
response.cookies.set('locale', 'en');
return response;
}
// ✅ response.cookies.set(name, value, options)
// ✅ Cookie options for security
// ✅ Multiple cookies supportedDeleting Cookies
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Delete cookie
response.cookies.delete('session');
// OR set to expire immediately
response.cookies.set('session', '', {
maxAge: 0,
path: '/',
});
return response;
}
// ✅ response.cookies.delete(name)
// ✅ Or set maxAge: 0Middleware File Location
Where to place middleware.ts
Select a file or folder to see details
Practical Middleware Examples
Example 1: Simple Authentication Check
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Protected routes
const protectedRoutes = ['/dashboard', '/profile', '/settings'];
const isProtectedRoute = protectedRoutes.some(route =>
pathname.startsWith(route)
);
if (isProtectedRoute) {
const session = request.cookies.get('session');
if (!session) {
// Not authenticated - redirect to login
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('returnTo', pathname);
return NextResponse.redirect(loginUrl);
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'],
};
// ✅ Check session cookie
// ✅ Redirect if not authenticated
// ✅ Preserve return URLExample 2: Logging and Analytics
export function middleware(request: NextRequest) {
const start = Date.now();
// Log request
console.log('Request:', {
method: request.method,
url: request.url,
userAgent: request.headers.get('user-agent'),
timestamp: new Date().toISOString(),
});
const response = NextResponse.next();
// Log response time
const duration = Date.now() - start;
response.headers.set('X-Response-Time', `${duration}ms`);
console.log('Response:', {
url: request.url,
duration: `${duration}ms`,
});
return response;
}
// ✅ Track all requests
// ✅ Measure response times
// ✅ Analytics dataExample 3: Mobile Redirect
export function middleware(request: NextRequest) {
const userAgent = request.headers.get('user-agent') || '';
const isMobile = /Mobile|Android|iPhone/i.test(userAgent);
const { pathname } = request.nextUrl;
// Don't redirect if already on mobile subdomain
if (request.nextUrl.hostname.startsWith('m.')) {
return NextResponse.next();
}
// Redirect mobile users to mobile site
if (isMobile && pathname === '/') {
const mobileUrl = new URL(request.url);
mobileUrl.hostname = `m.${mobileUrl.hostname}`;
return NextResponse.redirect(mobileUrl);
}
return NextResponse.next();
}
// ✅ Detect mobile devices
// ✅ Redirect to mobile subdomain
// ✅ Prevent redirect loopsMiddleware Best Practices
1. Keep Middleware Fast
// ✅ GOOD: Fast operations only
export function middleware(request: NextRequest) {
const hasSession = request.cookies.has('session');
if (!hasSession) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// ❌ BAD: Slow operations
export function middleware(request: NextRequest) {
// Don't make API calls!
await fetch('https://api.example.com/verify');
// Don't query databases!
await db.users.findUnique({ ... });
// Middleware runs on EVERY request
// Keep it fast!
}
// Middleware runs on every matched request
// Keep it lightweight and fast2. Use Matcher to Limit Scope
// ✅ GOOD: Specific matcher
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*'],
};
// ❌ BAD: Too broad (includes static files)
export const config = {
matcher: '/:path*',
};
// Exclude static assets:
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico).*)',
],
};
// Only run middleware where needed3. Return Responses Properly
// ✅ GOOD: Always return NextResponse
export function middleware(request: NextRequest) {
if (condition) {
return NextResponse.redirect(url);
}
return NextResponse.next(); // Don't forget this!
}
// ❌ BAD: Missing return
export function middleware(request: NextRequest) {
if (condition) {
NextResponse.redirect(url); // Missing return!
}
// Middleware must return a Response
}4. Use Absolute URLs
// ✅ GOOD: Absolute URL
return NextResponse.redirect(new URL('/login', request.url));
// ❌ BAD: Relative path
return NextResponse.redirect('/login'); // Error!
// Always use new URL() with request.url5. Handle Edge Cases
// ✅ GOOD: Handle edge cases
export function middleware(request: NextRequest) {
const session = request.cookies.get('session');
const { pathname } = request.nextUrl;
// Avoid redirect loops
if (pathname === '/login') {
return NextResponse.next();
}
// Protected routes
if (pathname.startsWith('/dashboard') && !session) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// Prevent redirect loops
// Handle all code pathsKey Takeaways
- middleware.ts in root - runs before every request
- matcher config - specify which routes to run on
- NextResponse.next() - continue to page/route
- NextResponse.redirect() - redirect with absolute URL
- NextResponse.rewrite() - serve different content
- Modify headers - request and response headers
- Read/set cookies - request.cookies and response.cookies
- Keep it fast - runs on every matched request
What's Next?
You've mastered middleware basics! Next, we'll explore Authentication with Middleware—building complete authentication systems, protecting routes, handling sessions and JWT tokens, implementing role-based access control, and creating production-ready auth flows. You'll secure your entire application!
We'll cover session management, JWT verification, protected routes, role-based permissions, and complete authentication patterns.
⚡ Edge Runtime
Middleware runs on the Edge runtime for incredible performance and global distribution. However, this means you have access only to Web APIs—no Node.js APIs like fs, crypto (use Web Crypto), or database drivers. Keep middleware logic simple and fast!