Edge & MiddlewareMiddleware Patterns
Middleware Patterns
Middleware runs on every request. Get the matcher wrong and costs explode.
Cost Trap
Middleware runs on every request - including static assets, images, API calls. A page with 30 assets = 30 middleware executions. Get the matcher wrong and costs explode.
The Matcher Is Everything
// ❌ BAD: Runs on everything
export const config = {
matcher: '/:path*',
};
// ⚠️ BETTER: Excludes static
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)'],
};
// ✅ BEST: Only paths that need it
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};Pattern: Geo Routing
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US';
if (['DE', 'AT', 'CH'].includes(country)) {
return NextResponse.rewrite(new URL('/de' + request.nextUrl.pathname, request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next|favicon.ico|.*\\..*).*)',],
};Pattern: A/B Testing
export function middleware(request: NextRequest) {
let bucket = request.cookies.get('ab-bucket')?.value;
if (!bucket) {
bucket = Math.random() > 0.5 ? 'control' : 'variant';
const response = NextResponse.next();
response.cookies.set('ab-bucket', bucket, { maxAge: 60 * 60 * 24 * 30 });
return response;
}
if (bucket === 'variant' && request.nextUrl.pathname === '/pricing') {
return NextResponse.rewrite(new URL('/pricing-v2', request.url));
}
return NextResponse.next();
}Pattern: Bot Blocking
const BLOCKED = ['AhrefsBot', 'SemrushBot', 'DataForSeoBot'];
export function middleware(request: NextRequest) {
const ua = request.headers.get('user-agent') || '';
if (BLOCKED.some(bot => ua.includes(bot))) {
return new NextResponse('Blocked', { status: 403 });
}
return NextResponse.next();
}Saves money by preventing scrapers from hitting your serverless functions.
Pattern: Auth Guard
export function middleware(request: NextRequest) {
// Quick check only
const token = request.cookies.get('session')?.value;
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*'],
};Do full token verification in the page (serverless), not middleware.
Audit Checklist
- Check Analytics → Edge Requests - burning through 10M quota?
- Review matcher - is it too broad?
- Question necessity - can auth be in layout? Redirects in next.config.js?
Do You Even Need Middleware?
Ask yourself:
- Can auth be checked in a layout instead?
- Can redirects go in
next.config.js? - Can geo logic be client-side for non-critical cases?
Often the answer is yes, and you save edge requests.
With runtime decisions made, see Caching for the highest-leverage optimization.