Introduction
When building modern web apps, performance, personalization, and security are no longer “nice-to-haves”—they’re expected. That’s where middleware in Next.js comes in.
Whether you need to check authentication before a page loads, redirect users based on location, or even A/B test your landing pages, middleware gives you the control you need before the request hits your actual route.
In this article, I’ll walk you through what Next.js middleware is, when to use it, and how to write your own.
What is Middleware in Next.js?
Put simply, middleware lets you run logic on the server before a request is completed—kind of like a gatekeeper for your routes.
It’s built on Edge Functions, which means it executes close to your users geographically, making it super fast and ideal for handling tasks like redirects, rewrites, and auth checks.
Unlike API route middleware (which happens inside your backend), Next.js middleware works for both pages and APIs at the routing level.
Why Should You Use Middleware?
Here are a few practical reasons I’ve found middleware useful in real-world projects:
- Redirect unauthenticated users to the login page.
- Personalize content based on country or language.
- Block bad bots or specific IPs before they reach your app.
- Rewrite URLs for cleaner, user-friendly paths.
- Split traffic for A/B testing or gradual rollouts.
File Structure & Setup
To get started, create a file named middleware.js (or middleware.ts) at the root of your project.
![Project Structure]()
Next.js will automatically detect and run this middleware for every request—unless you configure it to run only on specific paths (more on that later).
Basic Example: Authentication Redirect
Let’s say you want to block unauthenticated users from accessing /dashboard. Here's a simple example,
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const token = request.cookies.get('token')?.value;
const url = request.nextUrl.clone();
if (!token && url.pathname.startsWith('/dashboard')) {
url.pathname = '/login';
return NextResponse.redirect(url);
}
return NextResponse.next();
}
What’s Happening Here?
- We check if a token is present in the cookies.
- If the token is missing and the user tries to access a protected route (/dashboard), we redirect them to /login
Example: Redirect Based on User Location
Let’s redirect users from outside the U.S. to a localized version of the site.
export function middleware(request) {
const country = request.geo?.country || 'US';
const url = request.nextUrl.clone();
if (country !== 'US') {
url.pathname = `/intl/${country.toLowerCase()}`;
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
Note: No need for external services—Next.js provides request.geo for this out of the box.
Important Limitations
There are a few gotchas to keep in mind when using middleware.
- No access to the request body (req.body)
- You can’t use Node.js APIs like fs or crypto
- You can use Web APIs like Request, Response, URL, and cookies
So keep your logic simple, fast, and focused.
Best Practices
Based on experience, here are a few tips that help.
- Use matchers to scope middleware only to the routes that need it.
- Keep logic lightweight—middleware is great for simple checks, not heavy database queries.
- Leverage cookies and headers for user sessions and preferences.
- Avoid blocking rendering unnecessarily, especially if it can be done client-side.
When Shouldn’t You Use Middleware?
Don’t use middleware for things like,
- Theme toggles (client-side is better)
- Logging user actions (use analytics tools)
- Heavy data fetching (move that to server components or API routes)
Middleware is best for routing decisions, not rendering.
Final Thoughts
Middleware in Next.js opens up a new world of control. You can personalize experiences, secure your app, and make smarter routing decisions—all at the edge, before your app even loads.
It’s one of those features that, once you start using it, you wonder how you built without it.