middleware.ts running with zero console output and zero effect on requests
has three unrelated causes, and they fail silently in the same way — no error,
no warning, the request just passes through as if the file did not exist.
Check them in this order: file location, matcher config, then runtime
compatibility. Each one rules out roughly a third of real cases.
Check 1: the file is in the wrong place
Middleware only runs from a single, specific location — middleware.ts
(or .js) directly inside src/ if you use a src directory, otherwise
directly inside the project root, next to package.json. It is not
matched anywhere else, including app/middleware.ts or a nested folder:
✅ my-app/middleware.ts (no src dir)
✅ my-app/src/middleware.ts (with src dir)
❌ my-app/app/middleware.ts (never runs)
❌ my-app/src/app/middleware.ts (never runs)
There is no build error for a misplaced file — Next.js simply never
registers it as middleware. If moving the file to the correct location makes
it start running, this was the entire bug.
Check 2: the matcher config excludes your route
Middleware runs on every route by default unless you export a config
object with a matcher. A matcher that is too narrow — or written for a
different route shape — silently skips the requests you're testing:
// middleware.ts
export const config = {
matcher: '/dashboard/:path*', // only /dashboard/... requests hit this
};
export function middleware(request) {
console.log('middleware ran for', request.nextUrl.pathname);
}
Testing / or /login against this config will never log anything —
correctly, by the matcher's own rule. Two matcher mistakes cause most
"it just doesn't run" reports:
// ❌ matches only the literal path "/dashboard", not /dashboard/settings
matcher: '/dashboard'
// ✅ the :path* segment matches the exact route and everything under it
matcher: '/dashboard/:path*'
// ❌ forgot the API/static exclusions — middleware now runs on every
// _next/static and _next/image request too, which is usually not intended
// (this doesn't stop your route from matching, but it's the inverse bug —
// worth checking if middleware seems to run "for everything")
export const config = { matcher: '/:path*' };
// ✅ the documented catch-all pattern that excludes framework internals
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};
If you need multiple, independent route groups, matcher also accepts an
array — each entry is checked independently, so one overly strict pattern does
not block the others:
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*', '/api/protected/:path*'],
};
Check 3: an import that fails on the Edge runtime
Middleware runs on the Edge runtime, not Node.js, even though it is a
.ts file sitting next to your Node-only code. If middleware.ts imports
something that depends on Node APIs — fs, pg, most database drivers,
crypto in its Node form — the Edge bundler can fail to build the function,
and depending on your deployment target this can surface as the middleware
being skipped rather than a build error, especially with Cloudflare's Edge
runtime constraints.
// ❌ pg speaks raw TCP — not available on the Edge runtime
import { Pool } from 'pg';
export function middleware(request) {
const pool = new Pool(); // fails to run on the Edge
}
// ✅ read a cookie/header directly — no database round-trip in middleware
export function middleware(request) {
const token = request.cookies.get('session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
If middleware genuinely needs to check something a database holds (a
feature flag, a ban list), call an HTTP endpoint with fetch — the Edge
runtime supports fetch natively — instead of importing a driver that
expects a socket:
export async function middleware(request) {
const res = await fetch(new URL('/api/session-check', request.url), {
headers: { cookie: request.headers.get('cookie') ?? '' },
});
if (!res.ok) return NextResponse.redirect(new URL('/login', request.url));
}
Verifying the fix
- Confirm the file path with
ls middleware.ts src/middleware.ts— exactly one should exist, at the correct level. - Add a
console.log(request.nextUrl.pathname)as the first line and hit the exact route you expect to match; server logs (not the browser console) are where Edge middleware output appears. - Temporarily remove the
config.matcherexport entirely — middleware then runs on every route. If it starts firing, the matcher pattern was the bug; restore a corrected pattern rather than leaving the matcher off.
Related Incidents
- Fix: NextRouter was not mounted
- Fix "cookies() should be awaited" Error in Next.js 15
- Deploy Next.js 15 to Vercel Without Environment Variable Errors
- Next.js + Supabase SSR Session Management
- Fix Next.js Build Error Module Not Found After Deploy
Originally published at https://www.iloveblogs.blog
Top comments (0)