One-liner
apps/docs/app/page.tsx is a shadowed route. It redirects to /docs, but the runtime
never runs it: proxy.ts rewrites / to /en, so the homepage is always served by
app/[lang]/page.tsx. grep finds a root page that says "the root redirects to the
docs"; the site does not do that.
Observation class (dead code), not a user-visible bug. Filed unassigned for triage.
Measured — 2026-08-25, production build of apps/docs served with next start
$ curl -s -o /dev/null -w "%{http_code} %{content_type}\n" http://localhost:38413/
200 text/html; charset=utf-8
A 200 with the homepage. If app/page.tsx ran, this would be a 307 to /docs.
Both routes are built — the route table from the same next build lists them side by
side:
┌ ○ /
├ /[lang]
│ └ ● /en
Mechanism: proxy.ts's matcher is
'/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)'. For / the capture group
is the empty string, the dotted-path lookahead does not fire, so the proxy matches and
rewrites / to /en — app/[lang]/page.tsx, the real homepage. Nothing reaches
app/page.tsx.
Why it is worth recording
AGENTS.md "Route & surface ownership" rule 1 names exactly this shape: a shadowed
duplicate is "code that grep finds and the runtime never runs — the exact input that
makes an agent (or a human) reason confidently from dead code".
Concretely: the docs-site indexability epic #12243 has cards that add metadataBase,
canonical links and JSON-LD to the homepage (#12234, #12240). An agent that puts the
homepage's canonical link in app/page.tsx will ship a green PR that changes nothing
served.
Options for whoever picks this up
- Delete
app/page.tsx — the proxy owns /. Simplest, and makes the ownership
obvious. Check first that Next still builds with no root page.tsx (there is a
root app/layout.tsx). - Keep it and say so — if it is a deliberate no-proxy fallback (a deployment where
middleware does not run), the file should carry a comment saying that, since its
current comment ("Middleware should handle locale routing") reads as if the file were
live.
Either way the fix is one file. Do not change the proxy matcher as part of this — that
matcher is load-bearing for #12233 and belongs to that card.
Source
Noticed while implementing #12232 (robots.txt / sitemap.xml) against apps/docs;
outside that card's file surface, so recorded rather than fixed.
One-liner
apps/docs/app/page.tsxis a shadowed route. It redirects to/docs, but the runtimenever runs it:
proxy.tsrewrites/to/en, so the homepage is always served byapp/[lang]/page.tsx.grepfinds a root page that says "the root redirects to thedocs"; the site does not do that.
Observation class (dead code), not a user-visible bug. Filed unassigned for triage.
Measured — 2026-08-25, production build of
apps/docsserved withnext startA 200 with the homepage. If
app/page.tsxran, this would be a 307 to/docs.Both routes are built — the route table from the same
next buildlists them side byside:
Mechanism:
proxy.ts's matcher is'/((?!api|_next/static|_next/image|favicon.ico|.*\\..*).*)'. For/the capture groupis the empty string, the dotted-path lookahead does not fire, so the proxy matches and
rewrites
/to/en—app/[lang]/page.tsx, the real homepage. Nothing reachesapp/page.tsx.Why it is worth recording
AGENTS.md "Route & surface ownership" rule 1 names exactly this shape: a shadowed
duplicate is "code that
grepfinds and the runtime never runs — the exact input thatmakes an agent (or a human) reason confidently from dead code".
Concretely: the docs-site indexability epic #12243 has cards that add
metadataBase,canonical links and JSON-LD to the homepage (#12234, #12240). An agent that puts the
homepage's canonical link in
app/page.tsxwill ship a green PR that changes nothingserved.
Options for whoever picks this up
app/page.tsx— the proxy owns/. Simplest, and makes the ownershipobvious. Check first that Next still builds with no root
page.tsx(there is aroot
app/layout.tsx).middleware does not run), the file should carry a comment saying that, since its
current comment ("Middleware should handle locale routing") reads as if the file were
live.
Either way the fix is one file. Do not change the proxy matcher as part of this — that
matcher is load-bearing for #12233 and belongs to that card.
Source
Noticed while implementing #12232 (robots.txt / sitemap.xml) against
apps/docs;outside that card's file surface, so recorded rather than fixed.