CodeNotes
Back to library
backendJune 5, 20266 min read

HTTP Middleware

Learn what middleware is, how it intercepts requests in Next.js, and a step-by-step codebase setup guide.

HTTP Middleware

Why middleware is used

When building a web server, you write code to handle requests (e.g. logging users in, returning profiles). However, there are tasks that need to run for almost every single page or API endpoint:

  • Checking if the user is logged in.
  • Logging request details (HTTP method, URL, timestamp) for diagnostics.
  • Validating headers and CORS security rules.
  • Checking rate limits to prevent server crashes.

If you write these checks manually at the top of every handler function, your code becomes repetitive and difficult to maintain.

// AVOID THIS: Copy-pasting auth check in every handler
function getProfile(req, res) {
  if (!req.cookies.token) return res.status(401).send("Unauthorized");
  // fetch profile...
}

function getSettings(req, res) {
  if (!req.cookies.token) return res.status(401).send("Unauthorized");
  // fetch settings...
}

Middleware solves this by extracting these checks into shared, reusable steps that intercept requests before they reach your endpoint logic.


Core Specifications

  • Why it exists: It was created to intercept and process incoming HTTP requests (and outgoing responses) in a centralized pipeline before they reach specific endpoint handlers.
  • What problem it solves: It prevents repeating identical check routines (such as authentication audits, request logging, security headers, and rate limiting) across every separate controller file.
  • How it works internally: It functions as an interceptor pipeline at the server gateway or CDN edge level. Each middleware function receives the request object and a trigger callback (next), allowing it to either inspect and forward headers, redirect the route, or terminate the request immediately with an error code.
  • When to use it: Use it when you need to enforce global access controls, attach unique trace IDs, parse user geolocation for routing, enforce security configurations, or log request statistics across multiple endpoints.
  • How it is used in real projects: In modern production setups, Next.js middleware is deployed to edge networks to inspect cookies and redirect unauthenticated traffic away from private user portals without warming up regional server containers.

Mental Model

Think of middleware as a Security Guard standing at the entrance of a club.

  1. The Request is a visitor trying to enter the club.
  2. The Security Guard is the Middleware.
  3. The Endpoint is the party inside.

Before the visitor can enter the party, the guard inspects their ID card (Authentication) and logs their name (Logging). If anything is wrong, the guard blocks them at the door (401 Unauthorized or 429 Too Many Requests). If everything is fine, the guard says "next!" and lets them pass.

1. Request Inbound
GET /dashboard
2. Middleware Guard
Checks Auth Cookies
3. Route Handler
Executes Dashboard Code

Codebase Integration

Here is how to set up and configure global route middleware in a modern Next.js (App Router) project.

Step 1: Create the Middleware File

Create a file named middleware.ts in your src/ directory. Next.js automatically detects this file and executes it for every request.

// filepath: src/middleware.ts
// Purpose: Intercept incoming page/API requests and redirect unauthenticated traffic.

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

export function middleware(request: NextRequest) {
  // 1. Retrieve the session token from cookies
  const sessionToken = request.cookies.get('session_token')?.value;
  const { pathname } = request.nextUrl;

  console.log(`[Middleware Log] Request intercepted: ${request.method} to ${pathname}`);

  // 2. Perform authentication check
  if (pathname.startsWith('/dashboard')) {
    if (!sessionToken) {
      // 3. If missing token, redirect to login page
      const loginUrl = new URL('/login', request.url);
      return NextResponse.redirect(loginUrl);
    }
  }

  // 4. Pass execution to the next handler/page
  return NextResponse.next();
}

Step 2: Configure Route Matchers

By default, the middleware runs on every single route (including images, stylesheets, and favicon assets). To optimize performance, configure a matcher export to limit which paths the middleware intercepts.

// filepath: src/middleware.ts (continued)
// Purpose: Exclude static assets from triggering route redirection checks.

export const config = {
  matcher: [
    /*
     * Match all request paths except:
     * - api/auth (authentication APIs)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico, images (site icons)
     */
    '/((?!api/auth|_next/static|_next/image|favicon.ico|images).*)',
  ],
};

Step 3: Handle API Route Tokens (Optional)

If writing a backend API controller instead of a page, return a JSON error instead of a redirect.

// filepath: src/app/api/admin/route.ts
// Purpose: A secure backend API route checking credentials in headers.

import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const apiKey = request.headers.get('x-api-key');

  if (apiKey !== process.env.ADMIN_API_KEY) {
    return NextResponse.json(
      { error: 'Unauthorized. Invalid API Key.' },
      { status: 401 }
    );
  }

  return NextResponse.json({ success: true, data: 'Admin configurations fetched.' });
}

Practical Project Use Cases

1. Request Tracing for Distributed Debugging

In distributed systems (microservices), debugging which service failed a request is difficult.

  • Real-World Example: We write middleware that generates a unique UUID (correlation ID) for every request, injecting it into custom response headers and forwarding it. This guarantees that all logs generated during this request contain the identical trace key:
// filepath: src/middleware.ts
// Purpose: Inject unique request correlation IDs for debugging trace pathways.

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

export function middleware(request: NextRequest) {
  // 1. Generate or read existing correlation ID
  const correlationId = request.headers.get('x-correlation-id') || crypto.randomUUID();

  // 2. Set headers on request
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-correlation-id', correlationId);

  // 3. Pass headers to final API routes
  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });

  // 4. Expose the tracking ID to client headers for bug reports
  response.headers.set('x-correlation-id', correlationId);
  return response;
}

2. Geolocation-based Redirection

In international SaaS tools, routing clients to region-specific content is required.

  • Real-World Example: We check request headers populated by edge networks (like x-vercel-ip-country) inside Next.js middleware. If a user connects from France, we automatically redirect them to /fr without ever loading regional components on the primary server first.

Common Pitfalls

1. Forgetting to return NextResponse.next() or redirecting

If you write middleware logic and do not return a response (or next step), the request will hang. The browser loading spinner will spin indefinitely.

  • Fix: Ensure every logical branch returns a NextResponse command.

2. Infinite Redirect Loops

If you intercept all routes and redirect unauthenticated users to /login, the request to /login will also trigger the middleware, which will redirect it to /login again, causing an infinite redirect crash.

  • Fix: Exclude login, signup, and asset files from the authentication check or matching path config.

Interview Questions

When does Next.js middleware execute relative to API routes?

Next.js middleware executes before any routing matches occur, before page rendering, and before any API route handler is called. It runs directly on the edge server, allowing it to perform fast redirects and header checks without warming up backend lambdas or executing page assembly code.


Summary

  • Role: Runs code before a request is completed, intercepting headers and paths.
  • Flow: Ends the request early (e.g. 401 error) or forwards it with NextResponse.next().
  • Placement: Located in src/middleware.ts in Next.js projects.

Keep Learning

system-design

Redis In-Memory Caching

Why Redis is so fast, how in-memory caching works, and a step-by-step implementation guide.

7 min readRead