Middleware can be selectively applied using either the config.matcher array in the middleware file, or conditional logic inside the middleware function, with matcher providing better performance through early filtering
In Next.js, middleware can be applied to specific routes using two complementary approaches: the config.matcher export in your middleware.ts file, and conditional logic within the middleware function itself. The matcher config is preferred for performance because it runs at the edge before the middleware function is even invoked, filtering requests at the routing level. Inside the middleware, you can add additional conditional logic based on request details like headers, cookies, or URL patterns. This two-layer approach gives you both efficiency and flexibility.
The matcher config supports powerful pattern matching using path-to-regexp syntax. You can use :path* for zero-or-more segments, :path+ for one-or-more segments, and :path? for optional segments. Static routes are matched exactly. The matcher runs on the request URL path, not including query parameters. You can also use arrays to match multiple patterns, and negation patterns (prefixed with /) to exclude routes.
Even with precise matcher configuration, you'll often need additional conditions based on request details. Inside the middleware function, you can inspect cookies, headers, geolocation, and the full URL to make decisions. This is where you implement authentication checks, A/B testing, or geolocation-based redirects. The matcher ensures the middleware only runs for relevant routes, reducing unnecessary execution, while internal conditionals handle the specific logic.
Be as specific as possible: The matcher runs on every request that matches the pattern. Narrow your matchers to only routes that need middleware to improve performance .
Use negation patterns for static files: Always exclude _next/static, _next/image, favicon.ico, and other static assets to prevent middleware from running unnecessarily .
Order matters: Matchers are evaluated in the order they appear. Put more specific patterns first, then more general ones .
Test your matchers: Use a tool like https://path-to-regexp.online/ to verify your patterns match the intended URLs .
Consider edge cases: Remember that trailing slashes affect matching—/about and /about/ are different paths .
If you don't provide a config.matcher, Next.js middleware runs on every request to your application, including static files and API routes. This is rarely what you want and can significantly impact performance. Always define a matcher to limit execution. The most common pattern is to exclude static assets and API routes while including everything else, then add conditional logic inside for specific paths.
Authentication: matcher: ['/dashboard/:path*', '/profile/:path*'] to protect user areas .
Localization: matcher: ['/', '/about', '/products/:path*'] with geo-based redirects inside .
Maintenance mode: matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)' to cover all user-facing pages .
A/B testing: matcher: ['/', '/landing/:path*'] for homepage and landing pages .
API rate limiting: matcher: '/api/:path*' specifically for API routes .