Middleware
Intercept and modify requests before they reach your pages
🛡️ What is Middleware?
Middleware runs before every request completes, allowing you to modify responses, redirect users, add headers, or check authentication. It's perfect for protecting routes and handling cross-cutting concerns.
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
// Check authentication
const isLoggedIn = request.cookies.get('token')
if (!isLoggedIn) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
Key Concepts
Route Protection
Guard pages before they load
if (!auth) {
return NextResponse.redirect()
}
Edge Runtime
Runs globally at the edge
export const config = {
runtime: 'edge'
}
Path Matching
Target specific routes
export const config = {
matcher: '/dashboard/:path*'
}
Request Modification
Add headers, cookies, rewrites
response.headers.set('x-custom', 'value')
🔹 Basic Middleware Setup
Create a middleware.js file in your project root. This file will run before every request to your application.
// middleware.js (in project root)
import { NextResponse } from 'next/server'
export function middleware(request) {
console.log('Middleware running for:', request.url)
// Continue to the requested page
return NextResponse.next()
}
// Optional: Configure which paths to run middleware on
export const config = {
matcher: '/:path*' // Run on all paths
}
🔹 Authentication Check
Use middleware to protect routes by checking if users are authenticated before allowing access to protected pages.
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
const token = request.cookies.get('auth-token')
const { pathname } = request.nextUrl
// Protect dashboard routes
if (pathname.startsWith('/dashboard')) {
if (!token) {
// Redirect to login if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*'
}
🔹 Adding Custom Headers
Middleware can add custom headers to responses, useful for security headers, CORS, or custom metadata.
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
const response = NextResponse.next()
// Add custom headers
response.headers.set('x-custom-header', 'my-value')
response.headers.set('x-request-time', new Date().toISOString())
// Add security headers
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
return response
}
🔹 Redirects and Rewrites
Middleware can redirect users to different pages or rewrite URLs to serve different content without changing the browser URL.
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
const { pathname } = request.nextUrl
// Redirect old URLs to new ones
if (pathname === '/old-page') {
return NextResponse.redirect(new URL('/new-page', request.url))
}
// Rewrite: Show /blog content when user visits /articles
if (pathname.startsWith('/articles')) {
return NextResponse.rewrite(new URL('/blog', request.url))
}
return NextResponse.next()
}
🔹 Geolocation and Localization
Access user location data in middleware to provide localized content or redirect based on geographic location.
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
const country = request.geo?.country || 'US'
const { pathname } = request.nextUrl
// Redirect based on country
if (pathname === '/' && country === 'FR') {
return NextResponse.redirect(new URL('/fr', request.url))
}
// Add country to headers
const response = NextResponse.next()
response.headers.set('x-user-country', country)
return response
}
🔹 Path Matchers
Use the matcher config to specify exactly which paths should run your middleware, improving performance by avoiding unnecessary runs.
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
// Your middleware logic
return NextResponse.next()
}
// Multiple path patterns
export const config = {
matcher: [
'/dashboard/:path*', // All dashboard routes
'/api/protected/:path*', // Protected API routes
'/admin/:path*', // Admin routes
'/((?!api|_next/static|_next/image|favicon.ico).*)', // All except these
]
}
🔹 Rate Limiting
Implement basic rate limiting in middleware to prevent abuse by tracking request counts per IP address.
// middleware.js
import { NextResponse } from 'next/server'
const rateLimit = new Map()
export function middleware(request) {
const ip = request.ip || 'unknown'
const now = Date.now()
const windowMs = 60000 // 1 minute
const maxRequests = 100
// Get or create rate limit entry
const userLimit = rateLimit.get(ip) || { count: 0, resetTime: now + windowMs }
// Reset if window expired
if (now > userLimit.resetTime) {
userLimit.count = 0
userLimit.resetTime = now + windowMs
}
userLimit.count++
rateLimit.set(ip, userLimit)
// Block if over limit
if (userLimit.count > maxRequests) {
return new NextResponse('Too Many Requests', { status: 429 })
}
return NextResponse.next()
}