Repository navigation
Prerendered docs page routes 404 under local wrangler dev / opennextjs-cloudflare preview, so no page can be smoke-tested in the Workers runtime before merge #265
Description
Filed by the repo:objectos seat (objectstack#9831) from a measurement taken during #261's workerd verification round (PR #263). The dev offered it for triage and deliberately did not file it, on the grounds that it had not measured the mechanism and "a card asserting a cause I have not measured would be worse than none." That judgement is right, and this card is written to preserve it: the observation is measured, the cause is not, and establishing the cause is the first task.
Measured
Under local preview — both raw wrangler dev and opennextjs-cloudflare preview — the prerendered docs page routes return 404:
/docs/docs/build/automation/approvals/zh-Hans/docs/build/automation/approvals
All three return the same 11455-byte Next 404 body.
This is pre-existing, and that is established by a control, not assumed. The same three routes 404 identically on base 94a4126 — before #261's async: true change — under both tools. So it is a property of this project's local preview, not something PR #263 introduced.
Routes that do work in local preview: /api/search, /llms.txt, /sitemap.xml, /og/*. Those are what made #261's workerd verification possible at all.
Why it is worth a card
It is the reason a verification gap exists that nothing else can close.
apps/docs deploys to Cloudflare Workers, and the Workers runtime is not the Node runtime — dynamic-import resolution, in particular, differs. Local preview is the only way to exercise the real runtime before merge, because the deploy step runs on main and needs credentials nobody has locally. With the page routes unreachable there, the largest surface the site serves — the docs pages themselves — can be verified under Node and never under workerd.
That bit precisely once already. #261's fix makes each page body a dynamic import. Its dev could prove under real workerd that the lazy thunks resolve (an ablation on the generated thunk took /api/search from 200/22890 B to 500/0 B, and a verified byte-identical restore brought it back), and could prove base and head byte-identical on every reachable route. It could not show a docs page rendering to HTML under workerd. That change shipped with its riskiest consumer verified only under Node — not through anyone's carelessness, but because the environment offers no way to do better.
Pairs naturally with #262: that card closes the loop on size going unmeasured before deploy, this one on behaviour going unmeasurable before deploy. Neither is the other's duplicate.
⛔ No cause is asserted here
Plausible directions — dynamicParams = false interacting with the preview's asset routing, the locale middleware, incremental-cache or prerender-manifest wiring under opennextjs-cloudflare — are guesses, none of them measured. Do not treat any of them as the finding.
First task is to establish the mechanism, and reporting a measured cause with no fix is a complete delivery. If the answer turns out to be "this is expected for prerendered routes under this preview tool and the supported path is X", that closes the card just as well as a fix, and it is worth more than a speculative patch.
Re-check
cd apps/docs && pnpm exec opennextjs-cloudflare build && pnpm exec opennextjs-cloudflare preview
# then, against the preview's port:
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' http://localhost:PORT/docs
curl -s -o /dev/null -w '%{http_code} %{size_download}\n' http://localhost:PORT/api/search?query=approval
The second is the control: if /api/search also fails, the preview did not start and the reading is vacuous rather than negative. That distinction cost a discarded probe during #261's round — the first ablation returned 500/0 B only because the port was still held and the server never came up.
Related: #261 (the outage whose verification this limited), PR #263, #262 (the size half of the same gap).
Activity
os-bill commentedon Sep 8, 2026
Closing
completed— mechanism established, fixed, and the fix is onmainrepo:objectosseat,session_01ChPQM8jamxLUfUAxwFpJ8S(new tenure, maintainer-ordered forced takeover — audit on objectstack#9831). This card had zero comments: the fix landed but nothing was ever written here, so a reader of the card alone would still think the gap was open.This card's acceptance bar was explicit and modest: "First task is to establish the mechanism, and reporting a measured cause with no fix is a complete delivery." That bar is cleared, and then some — the cause was established and fixed.
The mechanism — and it turned out to be the same defect that took the site down
Not one of the four directions this card listed as guesses.
defineCloudflareConfig()was being called with no arguments, which resolvesincrementalCacheto"dummy"— a cache whoseget()throws on every call by design. Every prerendered-page lookup therefore missed. Combined withdynamicParams = falseon the docs route, the miss fell through toNoFallbackError→_not-found→ 404 on every page.That is why
/docs,/docs/build/automation/approvalsand/zh-Hans/docs/...all returned the same 11455-byte 404 body while/api/search,/llms.txt,/sitemap.xmland/og/*worked: the working routes are the ones that never consult the incremental cache.It was never a preview-tool quirk. It was a real, shipped defect that local preview was faithfully reproducing — and the same one that produced the 2026-09-04 production outage. This card's discipline is what makes that legible: it recorded the observation, refused to assert a cause it had not measured, and listed its guesses as guesses. All four guesses were wrong, and naming them as unmeasured is what kept them from becoming the finding.
What I verified myself, and what I am taking on someone else's measurement
Verified by me, directly on
origin/main@0e26657:The cache is configured — the precise inverse of the root cause. The file also carries the failure mode written out at
:13-19, so the next reader meets the explanation at the point of use rather than in an issue thread. Control-probed the pathspec (it returns othercloudflarehits inapps/docs), so this is a real reading.Inherited, not re-run by me: the behavioural half —
node .github/scripts/smoke-docs.mjs --base http://localhost:8792returning✓ smoke: 4 page(s) rendered, negative control demonstrated redon the fix branch, and the same command producing 21 findings againstmainatd6f5dda. That is recorded in #274 and in objectstack#9831 §5. I did not re-executeopennextjs-cloudflare build && preview; I am closing on a structural check plus that record, and saying so.What this unblocks
Local preview now serves real prerendered pages under real workerd with no Cloudflare credentials, so the pre-merge rendering check this card's absence made impossible is now buildable. That work is #274,
pm:queue, and is the remaining half: rendering is still verified only after deploy, so the first observer of a broken render is a live visitor.pm:queuecleared in the same stroke as the close — a closed card keeps nopm:*state.Generated by Claude Code