middleware.ts never runs — request goes directly to the route handler
appears when:When middleware.ts is in the wrong location, the matcher excludes the URL, or the project uses output: 'export' which disables middleware
Next.js middleware not running in production
Next.js ships middleware silently. Wrong location, wrong matcher, or static export and the file is skipped with no warning. The fix is always one of these three.
src/), the config.matcher pattern matches the request URL, and the project is not in static export mode. Verify all three before editing middleware logic — most non-running middleware is a location or matcher problem, not a code problem.Quick fix for Next.js middleware not running
01// middleware.ts — place at project root or src/ root02import { NextResponse, type NextRequest } from "next/server";03 04export function middleware(req: NextRequest) {05 // Example: gate /dashboard behind a session cookie06 const session = req.cookies.get("session");07 if (!session && req.nextUrl.pathname.startsWith("/dashboard")) {08 const url = req.nextUrl.clone();09 url.pathname = "/login";10 url.searchParams.set("from", req.nextUrl.pathname);11 return NextResponse.redirect(url);12 }13 return NextResponse.next();14}15 16// Matcher uses regex, not Next router params.17// This pattern excludes API routes, static files, and _next internals.18export const config = {19 matcher: [20 "/((?!api|_next/static|_next/image|favicon.ico).*)",21 ],22};Deeper fixes when the quick fix fails
01 · Matcher with has / missing modifiers for header-based routing
01// Match only authenticated requests (cookie set) on protected paths02export const config = {03 matcher: [04 {05 source: "/dashboard/:path*",06 has: [{ type: "cookie", key: "session" }],07 },08 {09 source: "/dashboard/:path*",10 missing: [{ type: "cookie", key: "session" }],11 },12 ],13};14 15// Pair with logic that branches on cookie presence — cleaner than16// checking cookies inside a single matcher that runs for both cases.02 · Edge-safe JWT verification with jose
01// middleware.ts — Edge-compatible JWT check02import { jwtVerify } from "jose";03import { NextResponse, type NextRequest } from "next/server";04 05const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);06 07export async function middleware(req: NextRequest) {08 const token = req.cookies.get("session")?.value;09 if (!token) return NextResponse.redirect(new URL("/login", req.url));10 11 try {12 await jwtVerify(token, SECRET);13 return NextResponse.next();14 } catch {15 return NextResponse.redirect(new URL("/login", req.url));16 }17}18 19export const config = { matcher: "/dashboard/:path*" };03 · Matcher regression test
01// tests/middleware-matcher.test.ts02import { describe, it, expect } from "vitest";03 04const MATCHER = new RegExp("^/((?!api|_next/static|_next/image|favicon.ico).*)$");05 06describe("middleware matcher", () => {07 it.each([08 ["/", true],09 ["/dashboard", true],10 ["/dashboard/settings", true],11 ["/api/user", false],12 ["/_next/static/abc.js", false],13 ["/_next/image?url=x", false],14 ["/favicon.ico", false],15 ])("matches %s -> %s", (url, expected) => {16 expect(MATCHER.test(url)).toBe(expected);17 });18});Why AI-built apps hit Next.js middleware not running
Next.js middleware has a rigid contract: one file at a specific location, one exported function named middleware, and an optional configexport with matcher rules. Any deviation and Next.js silently skips the file without a build warning. The silence is the hardest part — there is no log line saying "your middleware is not running", just a request that flows past your auth gate into the route handler unhindered.
Location is the first trap. If your project has a src/ directory (most do in 2026), middleware lives at src/middleware.ts. If not, it lives at the project root alongside package.json. Any other path — inside app/, inside lib/, inside a subdirectory of src/ — is ignored. AI scaffolds frequently put middleware next to the routes it guards, which feels right but is wrong. A move-up to the canonical location is often the only fix needed.
The matcher is the second trap. Matchers use a regex subset, not the Next.js route params syntax. Writing matcher: '/blog/[slug]' does not work — you need /blog/:path* or /blog/(.*). The negative lookahead pattern to skip internals is fiddly: /((?!api|_next/static|_next/image|favicon.ico).*). A missing paren or a typo in _next silently drops matches for every URL. Test matchers with real paths: the Next.js documentation has a matcher playground.
The third trap is static export. When next.config.ts contains output: 'export', the build produces pure static HTML and JS with no server runtime. Middleware requires the Edge runtime to intercept requests. In static export mode, middleware is stripped out of the build silently. Vercel still deploys the app successfully — just without middleware. If auth stops working after a recent next.config change, check for static export first.
The fourth trap, less common, is importing a Node-only library in middleware. Things like jsonwebtoken that use Node crypto instead of Web Crypto will error at runtime on Vercel's Edge, taking the whole middleware down. Swap to Edge-compatible alternatives like jose.
Next.js middleware not running by AI builder
How often each AI builder ships this error and the pattern that produces it.
| Builder | Frequency | Pattern |
|---|---|---|
| Lovable | Every middleware scaffold | Places middleware.ts inside app/ instead of at root |
| Bolt.new | Common | Uses route-param syntax in matcher instead of regex |
| Cursor | Common | Imports jsonwebtoken in middleware — fails in Edge runtime |
| Base44 | Sometimes | Adds output: 'export' to reduce deploy time, unaware it kills middleware |
| Replit Agent | Rare | Creates multiple middleware.ts files in subdirectories, only root runs |
Related errors we fix
Stop Next.js middleware not running recurring in AI-built apps
- →Keep middleware.ts at the canonical location — project root or src/ — never anywhere else.
- →Unit-test matcher patterns against a fixture of URLs so regex typos fail CI.
- →Use jose, not jsonwebtoken, for JWT operations in middleware.
- →Never combine output: 'export' with middleware — pick one or the other per project.
- →Keep middleware logic thin: cookie reads, redirects, rewrites. No DB calls, no heavy crypto.
Still stuck with Next.js middleware not running?
Next.js middleware not running questions
Where exactly should middleware.ts live in a Next.js project?+
Why does my middleware work in dev but not on Vercel?+
Can middleware call Node.js APIs like fs or crypto?+
Why does the matcher config not work with dynamic routes?+
How long does an Afterbuild Labs middleware audit take?+
Ship the fix. Keep the fix.
Emergency Triage restores service in 48 hours. Break the Fix Loop rebuilds CI so this error cannot ship again.
Hyder Shah leads Afterbuild Labs, shipping production rescues for apps built in Lovable, Bolt.new, Cursor, Replit, v0, and Base44. our rescue methodology.