Authentication patterns: sessions, JWTs and what to use when
The sessions-versus-JWT argument is really an argument about revocation. Decide how fast you need to be able to log someone out.
Next.js middleware is a function that runs before a request is completed, typically at the edge, and can rewrite, redirect, or modify headers and cookies. Use it for cheap routing decisions — auth gating, locale detection, A/B bucketing, security headers — and keep database queries, heavy logic and full session validation out of it.
Middleware is a single function in middleware.ts that runs before routing resolves, on every request matching its matcher config.
Because it runs before the cache and before rendering, it is the right place to decide where a request should go. Because it runs on every matched request, anything slow in it becomes latency added to your whole site.
// middleware.ts
import { NextResponse, type NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("session")?.value;
if (!token && request.nextUrl.pathname.startsWith("/app")) {
const url = new URL("/login", request.url);
url.searchParams.set("next", request.nextUrl.pathname);
return NextResponse.redirect(url);
}
return NextResponse.next();
}
export const config = {
matcher: ["/app/:path*", "/account/:path*"],
};Match the narrowest set of paths that need the logic, and always exclude static assets and image optimisation routes.
export const config = {
matcher: [
// Everything except static files, images, and metadata routes
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|.*\\.(?:png|jpg|svg|webp)$).*)",
],
};A broad matcher that includes _next/static means middleware runs for every JavaScript chunk and font file your page loads. On a page with 30 assets that is 30 unnecessary middleware invocations per visit — measurable in both latency and platform cost.
| Rewrite | Redirect | |
|---|---|---|
| URL in address bar | Unchanged | Changes |
| HTTP status | 200 | 307/308 (or 301/302) |
| SEO effect | Content served under the requested URL | Signals the canonical location |
| Use for | Locale paths, A/B variants, proxying | Moved pages, auth gating, canonical host |
For permanent URL changes during a site migration use a 308 (or 301) redirect so link equity transfers. Use a rewrite when two URLs should genuinely serve the same content and you do not want the visitor's URL to change.
Yes, if the path matches. That is precisely why the matcher matters — middleware can add latency in front of a page that would otherwise be served straight from the CDN.
It is not designed for it, and doing so forces buffering that undermines the point of running early and cheaply. Handle body inspection in a route handler.
Historically middleware ran only on the edge runtime with a limited API surface. Recent Next.js versions allow opting into the Node.js runtime for middleware, but the performance guidance is unchanged: keep it small and fast.
One per project, at the root (or inside src/). Compose multiple concerns by calling separate functions from that single entry point.
ROVQIX Engineering
Engineering team, ROVQIX
The ROVQIX engineering team builds and maintains web platforms, APIs and infrastructure for clients across SaaS, ecommerce and enterprise. These notes come out of real production work — deploys, incidents, migrations and audits.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
The sessions-versus-JWT argument is really an argument about revocation. Decide how fast you need to be able to log someone out.
Six headers, most of them one line each. They will not fix a vulnerability, but several of them turn one into a non-event.
Migrations lose traffic for one reason more than any other: a redirect map that was built quickly and never checked.
No spam. Just the occasional case study and craft breakdown.