HTTP Middleware
Learn what middleware is, how it intercepts requests in Next.js, and a step-by-step codebase setup guide.
Outline↓
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.
- The Request is a visitor trying to enter the club.
- The Security Guard is the Middleware.
- 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.
GET /dashboardCodebase 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/frwithout 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
NextResponsecommand.
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.tsin Next.js projects.