What is middleware? (the code that runs between request and response)
In one sentence:Middleware is code that runs between an incoming request and the page or endpoint that answers it, where your app checks logins, redirects visitors, logs traffic, or blocks requests before they reach your routes.
What it actually is
Middleware is code that runs in the middle: after a request arrives at your server, before routing hands it to the page or API endpoint that will answer it. Every request passes through it on the way in. That position makes it the natural place for checks that should apply everywhere, starting with “is this user logged in?”
Think of the security desk in an office lobby. Nobody walks straight to an office; everyone passes the desk first. The guard checks your badge (are you logged in?), points you to reception if you don’t have one (redirect), writes your name in the logbook (logging), or turns you away (block). The offices themselves never deal with any of that. Middleware is the desk, and your pages and endpoints are the offices.
The word covers two slightly different things depending on your stack. In Express, middleware is a chain of small functions with the signature (req, res, next), registered with app.use(). Each one does its job, then either calls next() to pass the request down the chain or sends a response and stops right there. In Next.js, it’s one file, middleware.ts, at the root of your project (Next.js 16 renamed it to proxy.ts, partly because the old name kept getting confused with Express middleware). One file, in front of every route.
Why your AI just did this
You asked for “only logged-in users can see the dashboard,” and your agent created a middleware file. That’s the sane way to build it: one checkpoint covering every /dashboard route beats copy-pasting a login check into each page and hoping you never forget one. The middleware reads the session cookie or JWT, then redirects to /login when it’s missing.
Auth is the big one, but agents reach for middleware whenever something must happen on every request: logging traffic, redirecting old URLs, applying a rate limit, or setting security headers. In Next.js the agent usually exports a matcher config next to the function, a list of the paths the middleware should run on. That little config matters more than it looks.
When you’ll run into it
The scary version: every page of your deployed site shows the same error at once. On Vercel that’s a 500: INTERNAL_SERVER_ERROR with the code MIDDLEWARE_INVOCATION_FAILED, and it means your middleware crashed before any route got a chance to run. One unhandled exception in that one file takes the whole site down with it, because everything sits behind it.
Then there are the sneaky versions:
- Your site loads but looks unstyled, or images vanish. Without a
matcher, Next.js runs middleware on every request, including the static files under_next/staticand your images. A redirect meant for pages then fires on your CSS too. - The browser shows
ERR_TOO_MANY_REDIRECTS. Classic loop: the middleware sends logged-out visitors to/login, but/loginitself matches the middleware, so it redirects again, forever. - An Express route hangs with no error at all. Some function in the chain neither called
next()nor sent a response, so the request waits and nothing arrives. - A deprecation note after upgrading Next.js: version 16 wants
middleware.tsrenamed toproxy.tsand ships a codemod for it,npx @next/codemod@canary middleware-to-proxy .
What to check
- Open the middleware file first when every page breaks at once; one file in front of all routes means one bug with site-wide reach
- Read the
matcherconfig and confirm it excludes static assets (_next/static,_next/image,favicon.ico); the Next.js docs ship a ready-made exclusion pattern for exactly this - Trace the redirect by hand for the page you redirect to; if
/logintriggers a redirect to/login, you found your loop - Check the deploy logs, not the page code, when you see
MIDDLEWARE_INVOCATION_FAILED; the crash happened before your page ever ran - Confirm every Express middleware either calls
next()or sends a response; a function that does neither leaves requests hanging silently - Keep a real auth check inside the protected route itself; the Next.js docs warn that middleware should not be your only line of defense and say to check auth close to your data
Say it like a dev
Instead of: “my whole site is broken, every page shows a 500” Say: “I’m getting MIDDLEWARE_INVOCATION_FAILED on every route. Can you check middleware.ts for an unhandled exception?”
Instead of: “the site keeps redirecting in circles” Say: “The auth middleware redirects to /login, but /login matches the middleware too. Exclude it in the matcher.”
Instead of: “make the dashboard private” Say: “Add middleware that checks the session on /dashboard/:path* and redirects to /login when it’s missing, and keep the auth check in the route as well.”
People actually ask
“Why did my AI add a middleware file to my project?”
Middleware is the one place that runs before every matching page or endpoint, so it's the natural home for checks that should apply everywhere, like requiring a login on all /dashboard routes. One checkpoint beats repeating the same auth check in every page and forgetting one. Agents also use it for redirects, logging, and rate limiting.
“Why does a middleware error break every page at once?”
Because middleware runs in front of all your routes, a crash in that one file stops every request before any page code executes. On Vercel this shows up as a 500 error with the code MIDDLEWARE_INVOCATION_FAILED on every URL. Fix the middleware file and the whole site comes back at once.
“Where does middleware live in my project?”
In a Next.js project it's a single file named middleware.ts (renamed proxy.ts in Next.js 16) at the project root or in src/, next to your app folder. In an Express server it's the functions registered with app.use() in your main server file, often index.js or server.js. The pattern holds across frameworks: it's registered once, near the entry point of the app.
Related terms
Checked against
Just met this in a real session? Glim reads it in plain words.
Paste what your agent just did, a git diff, your terminal, or its summary, and Glim tells you what changed and what to check before you ship. Nothing stored.
Explain my session →