Uh oh!
There was an error while loading. Please reload this page.
fix(docs): delete the shadowed root route; proxy.ts owns / - #12351
Merged
Conversation
`apps/docs/app/page.tsx` called `redirect('/docs')` and never ran. `proxy.ts`
rewrites `/` to `/en` before routing, so `app/[lang]/page.tsx` serves the
homepage. The file was code `grep` finds and the runtime never executes --
AGENTS.md "Route & surface ownership" rule 1 -- and its content was also wrong
for today's site, which no longer forwards `/` to the docs.
Measured on a local production build (`next build && next start`, Next 16.3.1),
four configurations, `GET /`:
page.tsx proxy GET / GET /docs
present present 200 homepage, 72117 B 200
absent present 200 homepage, 72117 B 200
absent absent 404 (Next default, 8686 B) 404
present absent 307 -> /docs, which is itself 404 404
Deleting it changes nothing served: the `/` response is byte-identical apart
from the Next build id, which changes on every build (`/docs`, untouched by
this diff, differs in exactly the same way -- the control).
Keeping it as a "fallback" was measured too, and it is not one. In the only
configuration where the file would run -- the proxy not matching `/` -- every
unprefixed path already 404s, `/docs` included, so the file rescues one URL out
of a 404 and into another. It also makes that failure quieter: with the file
present `/` answers 307 instead of 404, masking a proxy that has stopped
running. Absence must be loud (rule 3), so the honest end state is deletion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>os-zhuang
marked this pull request as ready for review
August 25, 2026 20:25
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#12255
apps/docs/app/page.tsxcontainedredirect('/docs')and never ran.proxy.tsrewrites
/to/enbefore routing, soapp/[lang]/page.tsxserves the homepage.It was code that
grepfinds and the runtime never executes — AGENTS.md"Route & surface ownership" rule 1 — and its content was also wrong for today's
site, which no longer forwards
/to the docs.The decision, and what decided it
The card left two honest end states open: delete the file, or keep it as a real
fallback. The deciding question was measurable — with
app/page.tsxdeleted,what does
/do under a production build, and is there a reachable configurationwhere deletion costs something? Four configurations were built and served.
What was measured
Local production build only. Production has not redeployed since ~16:55 on
2026-08-25 (#12333), so the live site is evidence about a two-hour-old build and
was not used. Each row is its own
pnpm --filter @objectstack/docs build(Next 16.3.1, exit 0) followed by
next startandcurl:app/page.tsxproxy.tsGET /GET /enGET /docs200 text/html, 72117 B, homepage, canonicalhttps://objectstack.ai307to/200, 211597 B200 text/html, 72117 B, homepage, same canonical307to/200, 211597 B404, 8686 B, Next default error page200homepage404307to/docs— and/docsis itself a404200homepage404Route table from the same builds: with the file present the build emits a
/entry; without it,
/is gone and/enis still prerendered under/[lang].The proxy line
Proxy (Middleware)is present in rows 1–2 and absent in rows 3–4(counted from the build logs, not assumed).
Byte-level check on row 2 versus row 1: the two
/responses are 72117 byteseach and differ in exactly two spans, both of them the Next build id
(
0YebRTq5hijtQuB6iqeqNversus3CUsAVIGaUla2OR4S-KdV), which changes on everybuild.
/docs— untouched by this diff — differs in the same way and only thatway, which is the control that identifies the difference as build-id noise rather
than a content change. RSC and prefetch requests to
/(RSC: 1,Next-Router-Prefetch: 1), a query-string request, andHEAD /were probed inboth rows and match status-for-status.
/sitemap.xmland/robots.txtarebyte-identical (md5 equal) across the two.
What would have changed the answer
A reachable configuration in which
/degrades and the rest of the site doesnot — that is what a fallback is for. Row 3 shows there is no such
configuration: when the proxy does not run, every unprefixed path 404s,
/docsincluded; only/en/**answers. So the file cannot be a partial-degradation safety net.
Row 4 measures the fallback option on its merits and it loses on its own terms:
with the file present and the proxy not running,
/answers307to/docs,which is a
404in that same configuration. The "fallback" forwards one URL outof a 404 and into another — and it makes the failure quieter, because
/answers
307instead of404while the site is comprehensively broken. That isthe wrong direction for AGENTS.md rule 3, "absence must be loud". Rewriting the
file to redirect to
/eninstead would have the same defect: it only matters ina configuration where nothing else works, and it would still soften the signal.
Had row 3 shown
/404ing while/docskept working, the fallback would havewon and this PR would have rewritten the file instead of removing it.
proxy.tsis untouched: the measurement says the fix does not belong there. Theproxy handles
/correctly, and its matcher is #12233's surface.Why it is worth doing at all
Epic #12243 has cards adding
metadataBase, canonical links and JSON-LD to thehomepage (#12234, #12240). An agent that puts the homepage's canonical in
app/page.tsxships a green PR that changes nothing served; that trap has had tobe written into three dispatch prompts as a warning. Deleting the file closes it
at the source —
grepno longer finds a root page to reason from.Verification
Gate union re-run after the final commit, at
afbe56173:pnpm check:docs-locale-catch-allVERDICT command-exit 0— "1 top-level dynamic segment(s), 1 guarded; dotted paths bypass proxy.ts: true"pnpm check:page-declaration-shapeVERDICT command-exit 0— "34 page entries across 2188 sources … all reach the kernel through a discoverable declaration"pnpm check:published-filesVERDICT command-exit 0pnpm check:test-source-aliasVERDICT command-exit 0pnpm check:type-source-resolutionVERDICT command-exit 0pnpm check:nul-bytesVERDICT command-exit 0— "scanned 6833 text file(s) … no raw ASCII control bytes"pnpm --filter @objectstack/docs typecheckVERDICT command-exit 0pnpm --filter @objectstack/docs buildtypescript.ignoreBuildErrors: false, so the build is a type gate too)The five
check:*families are the onesnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackderives for this diff; re-derived after the commit and unchanged.
Declared narrowing — lint.
pnpm lint(eslint . --no-inline-configoverthe whole repo) was not run; CI runs it. Narrowed to every hand-written source
file in the app this diff touches:
eslint apps/docs/app apps/docs/lib apps/docs/components apps/docs/proxy.ts --no-inline-config --format json→ 24 files linted, 0 errors, 0 warnings (count read from the JSON output, not
from a summary line). The narrowing excludes nothing that this diff could move:
eslint.config.mjsis unchanged and, by its own docblock, "never enablestype-aware linting (no
parserOptions.project, no typed@typescript-eslintrules) for ANY file", so no untouched file's verdict candepend on a file being deleted. The diff also adds and modifies zero files, so
the set eslint lints after this change is exactly the set before it, minus one.
Declared narrowing — verification ran UNLOCKED.
scripts/pm/os-verify-lock.shcould not take the shared verify lock on this host: no usable
flock(the lockis Linux-only; a stock macOS does not ship util-linux). Every command above ran
through the entry point, which reported
UNLOCKED (declared)each time. Noserialization guarantee held for these runs.
Landing notes
Docs-site only, publishes nothing — no changeset;
skip-changesetapplied.Nothing outside
apps/docs/app/page.tsxis touched:app/[lang]/**is #12240'ssurface right now and
proxy.tsis #12233's, and neither is in this diff.Generated by Claude Code