I shipped a one-line fix for a soft 404 yesterday. The commit is on main, it is deployed on the box, and the compiled server chunk contains it. The bug is still live. A garbage id under /vault/ answers HTTP 200 with a Not Found body, while the same garbage under /blog/ and /newsletter/archive/ answers 404. I checked four places. Three of them lied to me.
for p in /vault /blog /newsletter/archive; do
curl -sS -o /dev/null -w "%{http_code} $p\n" \
"https://thechosenvictor.com$p/zzz-does-not-exist-9x7"
done
# 200 /vault <- still broken
# 404 /blog
# 404 /newsletter/archive| Where I checked | What it said | Was it right? |
|---|---|---|
| Source on the server | dynamicParams = false | No |
| Deployed commit | d65e7b6, tree clean | No |
| Compiled chunk | dynamicParams:!1 | No |
| curl to the running site | 200 on an unknown id | Yes |
The gap is the build manifest. Next.js writes prerender-manifest.json at build time, and the router reads that file, not your source, to decide how a route behaves. /vault/[id] is missing from its dynamicRoutes list while both working siblings sit there with fallback false. So the router never learns it is a no-fallback route, falls through to on-demand rendering, runs notFound(), and returns that body with a 200. The flag is in the code, not in the thing that reads the code.
- Step 01
Ask what the running site returns, not what the repo contains
Source, commit hash, and compiled output describe intent. Only a request observes behaviour. When they disagree, the request wins.
- Step 02
Check the artifact your framework actually reads
For the Next.js App Router that is .next/prerender-manifest.json. If your route is missing from dynamicRoutes while its siblings are there, the config never reached the router.
- Step 03
Put the assertion after the deploy, not in the test suite
I have a passing test for this. It asserts the source exports the flag, which is true, so it cannot fail on this bug. A post-deploy curl against a random id, expecting 404, catches it the moment it ships.
Status, honestly
Open, not solved. In the last 24 hours 19 bogus vault ids took 33 hits, including AhrefsBot and SemrushBot, so crawlers are collecting these 200s while I write this. I am publishing the check before the cure, because a fix I have not watched work in production is not a fix yet.