CodeOath
← All posts
Next.js31 min total · 12 parts

Next.js Fundamentals: The App Router, Server Components, and the Caching Layer Nobody Warns You About

Part 10 of 12 · ~2 min

Middleware

Next.js Middleware is a single middleware.ts file at your project root that runs before a request reaches a route — page, Route Handler, or otherwise:

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  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*"],
};

"Runs at the edge" means this code executes in a lightweight runtime deployed geographically close to the person making the request, before that request ever reaches the servers actually running the bulk of your app — deliberately, so redirect and rewrite decisions happen with as little added latency as possible, on the smallest, fastest execution environment available.

Common real uses fall into a handful of recurring shapes:

  • Auth redirects — bouncing an unauthenticated request away from a protected route before it ever renders anything, as above.
  • A/B routing — rewriting a fraction of requests to an alternate version of a page based on a cookie or a random assignment, entirely transparently to the visitor's URL bar.
  • Geo-based logic — reading request.geo (or an equivalent header depending on your deploy target) to redirect visitors to a region-specific page or apply region-specific rules.

The real constraints

Middleware's speed comes at a real cost in capability, and it's worth being precise about the two constraints that matter most:

  • No direct database access. Middleware runs in a restricted runtime that does not support most Node.js APIs or standard database drivers (raw TCP sockets, most ORMs). If a middleware needs data-backed logic, that means calling an HTTP API (including your own Route Handler) rather than querying a database directly from inside it.
  • A limited runtime, not full Node.js. No access to the Node.js filesystem module, and only a constrained subset of what a normal server environment provides. Middleware is built for cheap, fast decisions — reading a cookie, checking a header, redirecting or rewriting — not for running business logic that needs the full Node.js standard library.

A mistake worth naming directly: reaching for middleware to do a database-backed permission check ("does this specific user own this specific resource") instead of a coarse, cookie-or-token-based check ("is there a valid session at all"). That fine-grained check belongs in the actual route or Server Action, which runs in a full server environment — middleware is for the cheap, coarse gate in front of it, not a replacement for real authorization logic deeper in the app.