Skip to main content
Next.js Middleware in 2026: Auth Guards, A/B Tests, and What Belongs at the Edge

Next.js Middleware in 2026: Auth Guards, A/B Tests, and What Belongs at the Edge

July 23, 2026
Frontend Engineering
5 min read

Key takeaways

Next.js Middleware (middleware.ts at the project root) intercepts every matched request before cache lookup, rendering, and route execution — the right layer for auth redirects, locale detection, and A/B cookie bucketing.

Middleware can read requests, set cookies, redirect, rewrite, or return an early response without the route running. Database queries and large npm imports do not belong here — both add latency to every matched request.

In 2026 on Vercel, Middleware runs on Fluid Compute (standard Node.js), not the old stripped edge runtime. The real constraint is latency: each added millisecond is paid on every matched request.

The matcher config scopes Middleware to specific routes. Without it, Middleware runs on every request including static files, adding overhead for no reason.

Auth in Middleware means verifying a self-contained JWT without a DB round trip. Full session validation belongs in the route or Server Component.

What Is Next.js Middleware and Where Does It Fit in the Request Lifecycle?

Next.js Middleware is a function exported from middleware.ts at the project root. It intercepts every matched request before Next.js resolves the route, before cached responses are served, and before any Server Component or Route Handler runs. It receives a NextRequest and returns a NextResponse: pass through (NextResponse.next()), redirect, rewrite (serve a different URL while keeping the original in the browser address bar), or a direct response that short-circuits the route.

Without a matcher config, Middleware runs on everything — including static files and images. The standard negative lookahead that excludes _next/static and image optimization routes is the correct starting point. On Vercel in 2026, Middleware runs on Fluid Compute (standard Node.js), so old Edge Runtime API restrictions no longer apply. Aim for under 10 ms execution time.

How Do We Write an Auth Guard in Next.js Middleware?

An auth guard reads a JWT from a cookie, verifies it locally with the jose library without a database call, and redirects to the login page if the token is missing or invalid. The JWT must be self-contained and signed — an opaque session ID requires a DB round trip, which breaks the latency budget for Middleware.

Two implementation details: the redirect target must be a URL object (new URL('/login', request.url), not a bare string), and when the token is invalid, delete the cookie on the redirect response to prevent a redirect loop from a malformed token being re-sent on every request.

How Do We Run A/B Tests in Next.js Middleware?

A/B testing in Middleware follows three steps: check for an existing bucket-assignment cookie, assign a bucket if absent, and rewrite the request to the variant URL. The rewrite keeps the original URL in the browser address bar — share links always point to the control route. Analytics read the cookie to attribute conversions to the correct variant.

Each variant lives in a private directory prefixed with an underscore, which Next.js treats as private and does not expose as a navigable route. Middleware rewrites /pricing to /_experiments/pricing/control or /_experiments/pricing/variant-b based on the cookie, transparent to the user.

What Can Middleware Read, Write, and Return?

Middleware receives a NextRequest with typed helpers: request.cookies, request.nextUrl, request.ip, request.geo (Vercel-only), and request.headers. We forward resolved values downstream by injecting custom headers into NextResponse.next() — this is how we pass a resolved locale or user ID to Server Components without re-running detection logic in every layout.

The four return types are: pass through (NextResponse.next()), redirect (browser navigates and URL changes), rewrite (browser URL unchanged, different content served), and a direct response (route never runs). Use redirect for auth flows and rewrite for A/B tests and path aliases.

What Should We NOT Put in Next.js Middleware?

Database queries are the most common mistake. Every matched request pays the DB round-trip cost before the route runs. Use a self-verifiable JWT in Middleware and leave full session hydration to the route. Large npm imports increase cold-start time for every Middleware instance — keep middleware.ts dependencies to a lightweight JWT library and nothing else avoidable. Network-dependent rate limiting turns a Redis timeout into a timeout on every matched request; put rate limiting in a dedicated Route Handler.

FAQ

**Q: Does Middleware run on static file requests?**

A: Only on routes matching config.matcher. Without a matcher it runs on everything including _next/static files. The standard negative lookahead pattern excludes static assets.

**Q: Can we query a database from Next.js Middleware?**

A: Technically yes, but every matched request pays the round-trip. Use a self-contained JWT for Middleware verification; leave DB calls to the route or Server Component.

**Q: What is the difference between NextResponse.redirect and NextResponse.rewrite?**

A: redirect sends a 307 or 308 and the browser navigates to the new URL — the address bar updates. rewrite serves different content while keeping the original URL in the address bar. Auth flows use redirect; A/B tests and path aliases use rewrite.

**Q: Can Middleware read the POST request body?**

A: No — buffering the body breaks streaming and prevents the route handler from reading it. Base auth and routing decisions on cookies, headers, and URL only.

**Q: Is the Edge Runtime still required for Middleware in 2026?**

A: Not on Vercel. Middleware now runs on Fluid Compute (standard Node.js). The practical constraint is latency budget, not API availability.