Next.js middleware: A name that misleads?
What is middleware?
If you’ve used frameworks like Express.js, you know middleware as functions that handle a request before the main business logic - checking authentication, adding headers, logging, or transforming the request.
Next.js brings its own take on middleware: a special file that lets you run code before a request reaches your pages or API routes. For example, you might want to redirect unauthenticated users:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Example: Redirect if user is not logged in
const isLoggedIn = /* logic to check cookie or header */
if (!isLoggedIn) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Continue the request as normal
return NextResponse.next()
}
Every request to your app passes through this file first (exception: you defined a config with matcher
), making it a global gatekeeper. But does it work like traditional middleware? Not exactly.
Classic middleware: The Express.js example
I choosed Express.js as a reference point because it’s the most popular Node.js framework and most people are familiar with it.
In Express, middleware functions are chained together for each request. You can stack as many as you wish, using next()
to move between them:
const express = require('express')
const app = express()
// Logging middleware
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`)
next() // Passes control to the next middleware or route handler
}
app.use(logger)
app.get('/', (req, res) => {
res.send('Hello world!')
})
Order and flexibility matter in Express - you can place and combine middleware wherever needed, giving you granular control.
Next.js middleware vs. “real” middleware: key differences
So, now that we’ve seen how middleware works in Express, let’s compare it directly to what Next.js calls "middleware."
Key differences between Express and Next.js middleware:
- No chaining or multiple layers: In Express, you can have as many middleware functions as you like and stack them however you need using
next()
. In Next.js, there’s only one middleware function per folder level, and there’s nonext()
to move between middlewares - it either returns a response or lets the request proceed. - Edge runtime restrictions: Next.js middleware runs on the Edge Runtime, which means you can’t rely on full Node.js APIs (no
fs
, no complex server logic). It’s designed for lightweight and fast operations, like authentication, redirects, or rewrites. - Single-entry, global checkpoint: Instead of being part of a flexible middleware pipeline, Next.js middleware acts as a checkpoint for all requests at a certain directory level. No matter what kind of resource someone’s requesting - page, API route, image - the middleware runs first, then passes the request on or responds early.
- Limited request/response mutability: In Express, you can modify the request and response on the fly - add properties, change headers, and more. In Next.js middleware, what you can do is more limited and often handled via
NextResponse
.
Here’s a quick visual comparison:
Feature | Express Middleware | Next.js Middleware |
---|---|---|
Multiple middlewares/chain | Yes (next() ) | No |
Full Node.js API access | Yes | No (Edge only) |
Context specificity | Per route/endpoint | Per directory (global) |
Request/response mutability | High | Limited |
Use cases | Logging, parsing, etc. | Redirects, auth, rewrites |
In short: While Next.js middleware does let you intercept and handle requests, it doesn’t offer the full flexibility (or complexity) of traditional server-side middleware stacks. It’s more of a lightweight gatekeeper than a true middleware layer.
And that’s why the term “middleware” in Next.js can be a little misleading - but let’s dive deeper into what that means, and why it matters...
How (and when) should you use Next.js middleware?
So, if Next.js middleware isn’t a drop-in replacement for classic server middleware, how should you actually use it? The answer: think of it less like a smart pipeline, and more like a gatekeeper for your app’s edge.
When to use Next.js middleware
Next.js middleware shines when you want to:
- Redirect users: For ex. send unauthenticated users to
/login
from protected routes. - Handle basic authentication and authorization: Check cookies, headers, or tokens to decide if someone should keep going.
- Rewrite or short-circuit requests: For A/B testing, country/language redirects, maintenance mode, and other quick changes.
- Handle simple analytics or logging: For example, counting visits or flagging suspicious patterns, as long as it’s lightweight.
Don’t use it for heavy logic, file uploads, or anything needing full Node.js support. For that, use API routes or server actions.
Tip: Keep your Next.js middleware small and fast.
Conclusion: Time for a new name?
To wrap things up: while the term “middleware” is familiar and convenient, in the world of Next.js, it comes with some quirks that can easily confuse developers - especially if you’re coming from an Express or classic Node.js background.
Next.js middleware is really an edge checkpoint, a simple way to intercept and reroute requests at the very start, but not a flexible, composable middleware chain like you might expect on the server.
So maybe it’s time for a new word? Edgeware? Edge handler? Request interceptor? Got a better name?
Thanks for reading 🚀