Next.js Middleware: Edge Functions, Redirects, and Request Interception

Next.js Middleware runs on the Edge Runtime, intercepting every request before it reaches your application. This means you can redirect users, rewrite URLs, check authentication, and serve different content based on geography — all at the CDN edge with sub-millisecond latency. Here is how to wield it effectively.
Middleware Fundamentals
A middleware.ts file at the root of your Next.js project exports a single middleware function. Every incoming request passes through it before hitting your pages or API routes.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US'
const response = NextResponse.next()
response.cookies.set('country', country)
return response
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
The matcher config is critical. Without it, middleware runs on every request including static assets, wasting edge execution budget. Always define a narrow matcher pattern.
Authentication and Authorization at the Edge
Checking authentication in middleware prevents unauthenticated users from ever reaching your pages. The pattern integrates naturally with NextAuth.js, Clerk, or custom JWT flows.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const PUBLIC_PATHS = ['/login', '/signup', '/api/auth']
export function middleware(request: NextRequest) {
const token = request.cookies.get('session-token')?.value
const { pathname } = request.nextUrl
if (PUBLIC_PATHS.some((path) => pathname.startsWith(path))) {
return NextResponse.next()
}
if (!token) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('redirect', pathname)
return NextResponse.redirect(loginUrl)
}
return NextResponse.next()
}
For session validation, consider using a lightweight JWT with short expiration (15 minutes) validated via a refresh token pattern. Avoid database lookups in middleware to maintain edge performance.
Geolocation Routing
Next.js Middleware has access to the request's geographic information through request.geo. Use this for region-specific content, currency display, or regulatory compliance.
export function middleware(request: NextRequest) {
const country = request.geo?.country
const region = request.geo?.region
// Redirect EU users to GDPR-compliant subdomain
const euCountries = ['DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'AT']
if (euCountries.includes(country ?? '')) {
const url = request.nextUrl.clone()
url.hostname = 'eu.soninow.com'
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
NextResponse.rewrite is subtly different from redirect. A rewrite serves content from the target URL without changing the browser's address bar. Use redirect when you want the URL to change, rewrite when you want to serve different content at the same URL.
A/B Testing at the Edge
Distribute traffic between page variants without client-side flickering. Store the variant assignment in a cookie so users see a consistent experience across visits.
export function middleware(request: NextRequest) {
const variant = request.cookies.get('ab-variant')?.value ??
(Math.random() < 0.5 ? 'control' : 'treatment')
const response = NextResponse.next()
if (!request.cookies.get('ab-variant')) {
response.cookies.set('ab-variant', variant, {
maxAge: 60 * 60 * 24 * 30, // 30 days
secure: true,
})
}
if (variant === 'treatment' && request.nextUrl.pathname === '/pricing') {
return NextResponse.rewrite(
new URL('/pricing-treatment', request.url)
)
}
return response
}
Request and Response Transformation
Middleware can modify request headers, add response headers, and transform responses. This is useful for adding CSP headers, injecting subresource integrity hashes, or handling CORS for API routes.
export function middleware(request: NextRequest) {
// Bypass for API routes needing CORS handling
if (request.nextUrl.pathname.startsWith('/api/')) {
const response = NextResponse.next()
response.headers.set('Access-Control-Allow-Origin', '*')
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
return response
}
// Security headers for page routes
const response = NextResponse.next()
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
)
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
return response
}
Performance Budget and Best Practices
Middleware runs on every matched request, so execution budget is tight. Vercel's Edge Runtime has a 25 ms CPU time limit per request. Keep these rules in mind:
- No heavy computation or large library imports
- Use
URLandHeadersAPIs — they run natively on the edge - Access
request.cookiesandrequest.headerssynchronously - Prefer simple pattern matching over complex logic
- Cache computed results in
request.cookiesor query parameters
Edge-Scale Request Handling
Next.js Middleware turns edge computing from a buzzword into a practical tool. Authentication, geolocation, A/B testing, and security headers happen before your application code runs — faster than any client-side or server-side alternative.
At SoniNow, we build Next.js applications that leverage edge middleware for performance and personalization. Our web development services cover middleware optimization, edge deployment strategies, and full-stack Next.js development.
Move logic to the edge. Partner with SoniNow to maximize your Next.js application's performance.
Related Insights

Authentication Patterns in Modern Web Apps: JWT, Sessions, and Passkeys
A guide to modern authentication patterns comparing JWT, session-based auth, and passkeys including implementation strategies, security considerations, and user experience.

CI/CD Pipeline for Next.js: GitHub Actions to Vercel and Docker Deployments
A step-by-step guide to building CI/CD pipelines for Next.js applications using GitHub Actions including automated testing, preview deployments, Docker builds, and production rollouts.

Code Splitting and Lazy Loading in React: Performance Optimization Guide
A comprehensive guide to code splitting and lazy loading in React applications including React.lazy, Suspense, route-based splitting, and component-level chunking.