Featured image of post Next.js Middleware: Routing, Auth, and Edge Computing Featured image of post Next.js Middleware: Routing, Auth, and Edge Computing

Next.js Middleware: Routing, Auth, and Edge Computing

Deep dive into Next.js middleware for routing, authentication, edge computing, geolocation, A/B testing, and Edge Runtime limitations.

Introduction

Next.js Middleware executes before every request, running at the Edge or serverless region. A single middleware.ts file at the project root intercepts incoming requests and can redirect, rewrite, manipulate headers, or perform authentication checks before the request reaches the matched route.

Middleware is ideal for fast, edge-based decisions that should happen before page rendering. It does not run on static assets or _next/static by default.

Basic Structure

The middleware file exports a function that receives NextRequest and returns NextResponse, along with a config object that defines which paths trigger execution.

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

export function middleware(request: NextRequest) {
  console.log(`Request: ${request.nextUrl.pathname}`);
  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

The matcher config uses path-to-regexp syntax with negative lookaheads to exclude static files. For section-specific middleware, use multiple patterns such as ['/dashboard/:path*', '/admin/:path*'].


Authentication and Authorization

Handling authentication in middleware is preferable to per-page checks because it provides a single source of truth and eliminates flickering on page transitions.

export function middleware(request: NextRequest) {
  const token = request.cookies.get("session")?.value;
  const { pathname } = request.nextUrl;

  if (!token && pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  if (token && pathname.startsWith("/login")) {
    return NextResponse.redirect(new URL("/dashboard", request.url));
  }

  return NextResponse.next();
}

For role-based access, decode the JWT or session token and check the user’s role before allowing access to admin routes. NextAuth.js, Clerk, and Auth0 all provide middleware helpers that simplify this process.


Request Rewriting and Redirects

Middleware supports two types of URL manipulation. Rewrites serve different content without changing the browser URL, while redirects change the URL entirely.

export function middleware(request: NextRequest) {
  const { pathname, hostname } = request.nextUrl;

  const tenant = hostname.split(".")[0];
  if (tenant && tenant !== "www") {
    request.nextUrl.pathname = `/${tenant}${pathname}`;
    return NextResponse.rewrite(request.nextUrl);
  }
}

Common use cases include localized landing pages, feature-gated content, and multi-tenant routing based on subdomain or cookie values.


Geolocation and Personalization

Edge middleware provides geolocation data through request.geo, which includes city, country, region, latitude, and longitude. The request.ip property gives the client IP address.

export function middleware(request: NextRequest) {
  const { country } = request.geo;
  const { pathname } = request.nextUrl;

  if (["DE", "FR", "IT", "ES", "NL"].includes(country) && !pathname.startsWith("/gdpr")) {
    return NextResponse.redirect(new URL("/gdpr", request.url));
  }

  return NextResponse.next();
}

Regional pricing, content localization, and GDPR consent routing are all practical applications of geolocation-based middleware logic.


Edge Runtime Limitations

Middleware runs on the Edge Runtime, which has important constraints:

LimitationDetail
Node.js APIsNot available (no fs, crypto, path)
Max execution30 seconds (aim for under 1 second)
Bundle size1 MB limit (code plus dependencies)
DatabaseNo direct connections; use API routes

Keep middleware logic lean. Use API routes or server components for heavy computation, database access, or file system operations.


Conclusion

Use middleware for fast, edge-based decisions like authentication, redirects, and URL rewriting. Keep logic simple since it runs on every matching request. Prefer the matcher config over conditional checks inside the function for performance. Test middleware behavior separately from page logic using tools like Playwright or Cypress.