Understanding Next.js Middleware: A Complete Guide
17 April 2025
World
Understanding Next.js Middleware: A Complete Guide
Read time: 7 min
Published: April 23, 2025
Author: CodeWorksLab
Figure 1. Middleware gives you control over every incoming request in Next.js.
Table of Contents
What Is Next.js Middleware?
Key Use-Cases
How It Works Under the Hood
Step-by-Step Example
Best Practices & Tips
Common Pitfalls
Performance Considerations
Conclusion & Next Steps
What Is Next.js Middleware?
Next.js Middleware is a special function that runs before every request is processed, on the Edge. It lives in the /middleware.js (or /middleware.ts) file at your project root and has direct access to:
- Request URL, headers & cookies
- Rewrite, redirect, or modify responses
- Run at the edge, with very low latency
In a nutshell: Middleware lets you intercept and transform requests globally, without touching your application routes or API functions.
Key Use-Cases
- Authentication & Authorization
- Redirect unauthenticated users to /login
- Protect admin or dashboard routes
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(req) {
const token = req.cookies.get('token');
if (req.nextUrl.pathname.startsWith('/dashboard') && !token) {
return NextResponse.redirect(new URL('/login', req.url));
}
return NextResponse.next();
}
- A/B Testing & Feature Flags
- Serve different variants based on headers or cookies
- Run experiments without touching client code
- Geo-Location
- Detect x-vercel-ip-country header
- Rewrite /us/pricing vs /eu/pricing
- Bot Protection & Rate-Limiting
- Block known bad user-agents
- Throttle requests based on IP
- Custom Headers & Caching
- Inject security headers (CSP, HSTS)
- Add Cache-Control for static assets
How It Works Under the Hood
- Placement: a single /middleware.js at project root (or per-directory).
- Execution: Edge Runtime, V8 isolates—no Node APIs allowed, but ultra-fast.
- Routing: You define matchers in next.config.js to scope which paths trigger middleware:
// next.config.js
module.exports = {
middleware: {
// only run on /dashboard and /api/*
matcher: ['/dashboard/:path*', '/api/:path*'],
},
}
- Return Values:
- NextResponse.next() → proceed as normal
- NextResponse.redirect(url) → send a 307/308 redirect
- NextResponse.rewrite(dest) → internally rewrite without changing URL
- You can also add/modify cookies and headers on the returned response object.
Step-by-Step Example: Protecting a Dashboard
Let’s build a middleware that guards /dashboard routes:
- Create /middleware.js at project root:
import { NextResponse } from 'next/server';
export function middleware(request) {
const { pathname } = request.nextUrl;
// 1) Only run on /dashboard
if (pathname.startsWith('/dashboard')) {
// 2) Check for an auth cookie
const token = request.cookies.get('auth-token');
if (!token) {
// 3) Redirect to /login
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', pathname);
return NextResponse.redirect(loginUrl);
}
}
return NextResponse.next();
}
- Configure matcher in next.config.js:
module.exports = {
middleware: {
matcher: ['/dashboard/:path*'],
},
};
- Test it out
- Visit /dashboard without the auth-token cookie → you’ll be bounced to /login?redirect=/dashboard.
- After login, set the auth-token cookie and you’ll be let through.
Best Practices & Tips
- Keep Logic Minimal Heavy computations in middleware slow down every request. Offload complex logic to API routes if possible.
- Use Matchers Scope your middleware narrowly to avoid unnecessary invocations.
- Leverage Cookies Use request.cookies.get() to read and response.cookies.set() to write cookies at the edge.
- Chainable Responses You can append headers easily:
const res = NextResponse.next();
res.headers.set('X-Frame-Options', 'DENY');
return res;
- Test Locally Run next dev and watch your middleware logs for each request. Edge errors will surface in your terminal.
Common Pitfalls
- Infinite Redirect Loops
- Don’t redirect /login back to itself. Always check and exclude the target path.
- Missing matcher
- Without it, middleware runs on every path—including static assets—adding unnecessary overhead.
- Using Node-Only APIs
- Edge Runtime does not support fs, net, etc. Stick to Web-standard APIs and @vercel/kv or fetch.
- Over-Rendering
- Don’t call external APIs from middleware—this will block your response. Instead, store small lookup tables in edge-cache or KV.
Performance Considerations
- Cold Starts at the edge are fast (~5 ms), but doing heavy work adds latency.
- No bundler warnings but you can still tree-shake unused code.
- Monitor your Edge Observatory in Vercel or use custom logging to measure request.time.
Pro Tip: If you only need rewrites or redirects, consider using next.config.js Redirects/Rewrites instead of middleware for zero-code edge config.
Conclusion & Next Steps
Middleware in Next.js opens up incredible possibilities—authentication, A/B tests, geo-routing, and more—all at the edge. To keep users engaged, follow these steps:
- Start small: protect one route or add a single header.
- Benchmark before and after (use Vercel Analytics & Lighthouse).
- Iterate: add matchers, caching, and minimal logic.
- Share your learnings: write a mini-tutorial on your blog or tweet your code snippet!
Enjoyed this deep dive? ⭐️ Star CodeWorksLab on GitHub and subscribe for more in-depth Next.js tutorials!