Match only requests that need a decision
Middleware runs before the route handler or page for the paths it matches, which makes it useful for routing, locale selection, lightweight authorization gates, experiments, and response headers. It also makes an overly broad matcher expensive and risky. Start by naming the small set of route families that need the behavior. Exclude static assets, image optimization paths, health checks, and public endpoints unless there is a concrete reason to include them.
import { NextResponse } from "next/server";
export function middleware(request) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-request-id", crypto.randomUUID());
return NextResponse.next({ request: { headers: requestHeaders } });
}
export const config = {
matcher: ["/dashboard/:path*", "/settings/:path*"],
};Write matcher rules as product policy, then test representative URLs. A catch-all expression may look convenient but can accidentally intercept callbacks, assets, or deployment probes. If the objective is a security header for every document response, prefer the platform's header configuration where it is simpler and easier to audit.
Make the middleware decision a clear access boundary
Authentication identifies a requester; authorization decides whether that requester may access a specific resource. Middleware can perform an early redirect when a session is missing, but it should not become the only authorization control. Route handlers and data access still need resource-level checks because requests can arrive through paths or service calls that bypass a browser navigation.
Public routes
Proceed without session checks; keep their matchers out of the protected set.
Protected routes
Validate a trusted session and send an unauthenticated visitor to a deliberate sign-in path.
Sensitive actions
Enforce tenant, role, and capability rules again beside the data or action.
Use a server-verified cookie or token, never a client-provided role flag. Keep return URLs local and validated, and retain the original path only when it is safe to send a user back there. Our Next.js authentication guide covers the session and RBAC design that should sit behind this early gate.
Design for the edge runtime you actually have
Middleware commonly runs in an edge-like runtime with different APIs, cold-start behavior, dependency support, and network constraints from a long-lived Node.js server. Treat it as a fast decision point. Decode and verify only what is necessary, avoid large SDKs and database lookups on every request, and set a small time budget for any unavoidable remote call.
- Keep dependencies small and compatible with the runtime your host documents.
- Do not rely on process-local memory for sessions, flags, rate limits, or mutable state.
- Do not place secrets in rewritten URLs, response headers, logs, or browser-readable cookies.
- Use a stable request ID to connect middleware decisions with downstream logs.
When a request needs database access or a multi-step policy decision, let middleware route it to a normal server boundary rather than silently turning the edge into an application bottleneck. For safe secret handling across that boundary, see our Next.js environment-variables guide.
Prevent redirect loops and unsafe rewrites
A redirect changes the browser URL; a rewrite serves a different route while preserving it. Both are powerful, and both need guardrails. First decide whether a user, crawler, API client, or internal callback should receive the behavior. Then make the decision idempotent: the destination must not immediately satisfy the same condition and redirect again.
Use permanent redirects only for durable URL moves. Authentication redirects, experiments, locale selection, and maintenance states are temporary and should retain an appropriate status. If middleware adds headers such as CSP or request identifiers, verify that it does not overwrite headers set later in the response pipeline. The staged rollout guidance in our Next.js CSP guide is a useful pattern for sensitive response changes.
Release middleware as a measurable infrastructure change
Because middleware can affect a large portion of traffic, release it with the same care as a gateway rule. Start with a narrow matcher or a non-blocking observation mode. Log a stable decision name, route template, outcome, latency, and release identifier—never the raw token or personal data that informed the decision. Compare the error rate, redirect rate, and latency against a baseline before broadening the rule.
Build route-level tests for public routes, protected routes, callback endpoints, static assets, expired sessions, deep links, and the redirect destination. In staging, test the actual hosting runtime rather than only a local Node.js environment. If a rollout needs targeted exposure, combine it with the controlled cleanup rules in our Next.js feature-flags guide.
Next.js middleware production checklist
✓ Matchers name only the routes that need middleware
✓ Static assets, callbacks, and probes are deliberately excluded
✓ Middleware handles early gates; routes still authorize resources
✓ Edge-compatible dependencies and short time budgets are used
✓ Redirect destinations cannot re-trigger the same redirect
✓ Return URLs are local, validated, and free of sensitive data
✓ Decision logs include safe, stable operational fields
✓ Staging and rollout tests cover real hosting behavior
Make every request boundary dependable
Endurance Softwares helps teams build secure, high-performing Next.js applications with practical routing, authentication, observability, and release controls.