Next.js 16 middleware returns 404 sitewide on Vercel

Deployment status shows Ready, but all routes return 404 with no runtime logs. Happens with both middleware.ts and proxy.ts patterns, and persists even when middleware is explicitly pinned to Node.js runtime.
Environment

  • Framework: Next.js 16.2.4 (App Router, Turbopack)
  • Runtime target: Node.js (pinned via export const runtime = "nodejs" in middleware.ts)
  • Project plan: Hobby
  • Root directory: dca/ (monorepo-style, Next.js app in subfolder)
    Build output
    Build succeeds cleanly. Route table shows all routes discovered:
    Route (app)
    ┌ ○ /
    ├ ○ /_not-found
    ├ ƒ /analysis/[key]
    ├ ƒ /api/analyses
    ├ ƒ /api/analyses/[key]
    ├ ƒ /api/analyze
    ├ ƒ /api/auth
    ├ ƒ /api/chat
    ├ ƒ /api/export
    ├ ƒ /api/extract
    ├ ƒ /api/preview
    ├ ƒ /api/save
    ├ ƒ /api/version
    ├ ƒ /api/writer-notes
    â”” â—‹ /login
    Build error observed
    With middleware.ts (both with and without export const runtime = "nodejs"):
    The Edge Function “middleware” is referencing unsupported modules:
  • vc__ns/0/dca/middleware.js: @/lib/session
    The imported @/lib/session module uses only Web Crypto APIs (crypto.subtle.importKey, HMAC-SHA256, sign) — no node:crypto, no fs, no other Node-only imports. Manually confirmed by reading the source.
    Pinning middleware to Node.js runtime (export const runtime = "nodejs" at top of middleware.ts) should bypass the Edge bundler entirely, but the same error appears in the build output regardless.
    Symptoms at runtime
  • All routes return 404 NOT_FOUND (example ID: sfo1::jqlpd-1776919508683-c3e88181f651)
  • Runtime Logs show zero entries per request — suggests requests aren’t reaching any deployed function
  • Confirmed Vercel Authentication / Deployment Protection is disabled
  • Tested in fresh incognito windows; same result on production URL and deployment-specific URL
    What I’ve tried
  1. Migrated middleware.ts → proxy.ts per Next.js 16 deprecation notice (self-contained, no imports from other modules) — same 404 behavior
  2. Reverted to middleware.ts, added explicit export const runtime = "nodejs" — Edge bundler error returns
  3. Confirmed root directory is dca, all env vars set for Production environment, Next.js 16.2.4 detected in build logs
  4. Forced redeploys with cache cleared — no change
    Questions
  • Is the runtime: “nodejs” pin being honored by Vercel’s Edge bundler in this deployment path? The persistent Edge-related error suggests possibly not.
  • What TypeScript compilation artifacts or transitive references in Web Crypto-only code might be tripping Vercel’s Edge-detection heuristic?
  • Is there a known workaround for Next.js 16 middleware on Vercel when the Edge bundler rejects a module that is actually Edge-compatible?
    Repo is private; happy to share details via DM or recreate a minimal reproduction if helpful.Questions that get answered the fastest are the ones with relevant info included in the original post. Be sure to include all detail needed to let others see and understand the problem! →

There’s another community post with 404 debugging tips that might be helpful. Please give these solutions a try and let us know how it goes.

A human should be around soon to offer more advice. But you can also get helpful information quickly by asking v0.

This piqued my interest, I’m doing some research and will have an answer soon!

When Vercel shows Ready but you get a 404 with zero logs, it means the Vercel Edge Network (the routing layer) is failing to map your incoming URL to the underlying serverless functions. Basically the request dies before it can even be processed. To answer your first question: No, the runtime: "nodejs" pin is not being honored for Middleware. In Next.js, middleware.ts is architecturally bound to the Edge Runtime. Unlike API routes or Page routes where you can toggle between Node.js and Edge, Middleware must be lightweight enough to run at the edge of the network.

  • Adding export const runtime = “nodejs” in middleware.ts is effectively ignored by the Vercel builder for that specific file.
  • The persistent “unsupported modules” error confirms the Vercel bundler is still trying to package it for the Edge, and it’s finding something it doesn’t like in @/lib/session.

Even if your code uses only crypto.subtle, the Turbopack/Webpack bundler often injects polyfills or crawls transitive dependencies that are non-Edge compliant. Common culprits include aliasing (sometimes the @/ alias resolution in a monorepo-style root (dca/) pulls in a package.json or a config file that references Node.js built-ins), object property access (if @/lib/session or any of its sub-dependencies references process.env in a way that isn’t statically replaceable, or uses Buffer (which is Node-only), the Edge bundler will flag it), or barrel file problems (if @/lib/session is exported from an index.ts that also exports something using fs or path, the bundler may attempt to pull the whole family tree into the Middleware bundle).

Since you are seeing a clean Route Table in the build logs but getting 404s at runtime, the issue is likely a Path Mismatch stemming from your root directory configuration (dca/). When Next.js is in a subfolder of a monorepo, the Vercel routing configuration (_redirects or the internal routing manifest) can become misaligned. If Vercel thinks the app starts at / but the build output is structured to expect /dca, or vice versa, the Edge Network won’t find the entry point.

My recommended workarounds are as follows:
If you must use session logic in Middleware, you need to ensure it is strictly isolated. Move the Web Crypto session logic into a standalone file (e.g., lib/edge-session.ts) that has zero other imports. If you aren’t already, use the jose library for JWT/Session handling. It is the gold standard for Edge-compatible Web Crypto operations and is what NextAuth/Auth.js uses to avoid these exact bundler errors.

Because you are using a subfolder (dca/) as the root: Ensure the Vercel Project Settings has the “Framework Preset” set to Next.js. If it’s on “Other,” the routing manifest won’t generate correctly. If you have a custom next.config.js in dca/, ensure you haven’t manually changed the distDir to something Vercel doesn’t expect. Alternatively, a more nuclear option would be to temporarily delete middleware.ts entirely and redeploy. If the 404s persist, the issue is 100% your Root Directory/Project Settings on Vercel. If the site comes back online, the issue is that the Edge Function build failure is causing the entire routing manifest to fail to deploy, resulting in a fallback to 404.

Is your dca/ folder a standalone Next.js project, or are you sharing a package.json with the root of the monorepo?

Thanks for the detailed response — really appreciate you taking the time.

Quick update: I’m executing the “nuclear option” you mentioned — stripping middleware entirely and moving auth into the route handlers and a protected server component layout. The root cause turned out to be the known Next.js bundler issue where next/server pulls ua-parser-js into the Edge bundle, which trips __dirname is not defined. Fighting the bundler didn’t seem worth it when the route-handler pattern avoids the problem surface entirely.

To answer your question: dca/ is a standalone Next.js project, not a monorepo — single package.json, Vercel Root Directory points at it, Framework Preset is Next.js. So the routing manifest side looks clean.

I’ll report back once the new deploy is live in case it’s useful for anyone else hitting this.

Update — resolved.

Ended up going with the “nuclear option” you mentioned: stripped middleware entirely and moved auth into route handlers + a protected server component layout (App Router route group). That cleared the original __dirname is not defined error cleanly. Root cause was the known Next.js bundler issue where next/server pulls ua-parser-js into the Edge bundle — not worth fighting when a route-handler auth pattern avoids the problem surface entirely.

However, after shipping Plan B, every URL still returned 404: NOT_FOUND — including direct deployment URLs in incognito. Clean build, all routes listed in the build output, no vercel.json, no basePath or assetPrefix in next.config.ts, all deployment protection off. The build output was correct; Vercel just wasn’t serving it.

What finally worked: deleted the Vercel project and re-imported it fresh from GitHub. Same repo, same Root Directory (dca), same env vars — deployed clean on the first try. My best guess is that the chain of failed deploys over the prior 48 hours corrupted the project’s internal routing manifest in a way that no amount of cache-clearing or redeploying would resolve.

To answer your earlier question: dca/ is a standalone Next.js project, not a monorepo. Single package.json, Root Directory pointed at it, Framework Preset set to Next.js. That part was always configured correctly.

Posting this in case anyone else lands here with the same combination: Next.js 16 + middleware __dirname error + persistent 404s after architectural changes. Order of operations that worked for me:

  1. Remove middleware entirely; move auth to route handlers and a protected layout

  2. Verify locally that build is clean and auth flow works

  3. If production still 404s with a clean build → delete and re-import the Vercel project before chasing config

Thanks for taking the time to respond — appreciated the push to rule out monorepo/root directory as a cause early.

1 Like

You’re very welcome! Glad to hear everything is working now!