Skip to content

fix(docs): emit the locale-independent .mdx URL from the docs page - #211

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-207-locale-mdx-links
Aug 26, 2026
Merged

fix(docs): emit the locale-independent .mdx URL from the docs page#211
hotlong merged 1 commit into
mainfrom
claude/issue-207-locale-mdx-links

Conversation

@hotlong

@hotlong hotlong commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #207

Every docs page rendered its Copy Markdown button and its Open popover with a target derived as page.url plus .mdx. On a locale-prefixed route page.url carries the prefix, and next.config.mjs rewrites exactly one shape — /docs/:path*.mdx to /llms.mdx/docs/:path*. A prefixed URL matches no rewrite, falls through to app/[lang]/docs/[[...slug]] with architecture.mdx as a slug, and is answered by /_not-found under dynamicParams = false.

The fix derives the URL from page.slugs through the existing docsPath helper and binds it once, so both controls take the same value and a third control added later inherits it rather than re-deriving one from page.url.

next.config.mjs is untouched. The diff is one file.

Why the prefix is dropped rather than the rewrite widened

This is a conformance decision, not the cheaper of two repairs. The machine-facing markdown surface here is English-only by AGENTS.md rule 1 — the same pin llms.txt and llms-full.txt carry, enforced by check-locale-surface.mjs. /llms.mdx/docs/[[...slug]] is English-only by construction too: its handler calls source.getPage(slug) with no language argument, so the default language is the only thing it can serve, and it prerenders 79 bodies rather than 553.

A /:lang/docs/:path*.mdx rewrite would therefore not deliver translated markdown. It would either serve English from a URL claiming a locale, or turn /llms.mdx into a locale-aware surface and put it out of step with the two llms files beside it. Reversing that policy is a decision about the whole markdown surface, not a 404 fix.

The third consumer: filed, not fixed — #210

The card named two consumers. The sweep found a third, and it is not repaired by this PR, deliberately.

/llms.txt states two rules with nothing scoping the first to English: "Every page below is also available as Markdown by appending .mdx to its URL", and then, in Other Languages, "Every page above is also published under a locale prefix — for example https://docs.objectos.ai/zh-Hans/docs/quickstart". Composing them is exactly what a machine reader does with an llms.txt, and it produces the same 404 this PR fixes for the in-page controls. Measured on the served file: /zh-Hans/docs/quickstart.mdx, /ja/docs/architecture.mdx and /de/docs/quickstart.mdx all 404, while /docs/quickstart.mdx answers 200 text/markdown.

A related trace sits in apps/docs/app/llms-full.txt/route.ts line 20, a comment asserting "The locale text stays reachable per page through the .mdx rewrite" — measured false; no locale text is reachable through that rewrite at all.

Both are outside this card's declared surface, and the repair is a wording change to a machine-facing contract file, which deserves its own review rather than a rider here. Filed as #210. Leaving it visible is the point: repairing two of three would have removed the symptom that leads someone to the third.

Verification

Against next start on a forced production build, reading markdownUrl out of the served payload. markdownUrl reaches the browser only as a client-component prop inside the RSC flight payload — it appears in no rendered markup — so extracting it is a deliberate read of the script bodies. The opposite check below strips those bodies first.

The three named pages — before, on ffe36c5, and after:

page markdownUrl before answered markdownUrl after answers
/docs/architecture /docs/architecture.mdx 200 /docs/architecture.mdx 200 text/markdown, first line # Architecture
/zh-Hans/docs/architecture /zh-Hans/docs/architecture.mdx 404 /docs/architecture.mdx 200 text/markdown, first line # Architecture
/ja/docs/architecture /ja/docs/architecture.mdx 404 /docs/architecture.mdx 200 text/markdown, first line # Architecture

Exhaustive sweep, every docs page in every locale — 79 paths enumerated from the served sitemap.xml, times 7 locales:

page renders checked      : 553
distinct .mdx targets     : 79
locale-prefixed target    : 0
english target moved      : 0
two controls disagree     : 0
no markdownUrl in payload : 0
target non-200/redirected : 0
target not text/markdown  : 0
target body not markdown  : 0

553 renders collapse onto 79 distinct targets — one per logical page — which is the locale-independence claim measured rather than asserted. "english target moved: 0" is the regression check: on the default locale the old expression was literally page.url plus .mdx, and page.url is the path being requested, so every English page is asserted to still point exactly where it did. The docs index edge case is included: /docs emits /docs.mdx, which answers 200 text/markdown.

Controls, all 200 with no redirect and no locale prefix, byte counts identical before and after:

control result
/docs/architecture.mdx 200, 0 redirects, text/markdown, 12488 bytes
/llms.txt 200, 0 redirects, 14557 bytes
/llms-full.txt 200, 0 redirects, 685243 bytes
/sitemap.xml 200, 0 redirects, 299145 bytes
/robots.txt 200, 0 redirects, 117 bytes

The visible page is unchanged. Every script body stripped before any comparison — a raw comparison would read the RSC payload, which is the one thing that legitimately changed. All three pages byte-identical after normalising content-hashed asset filenames, which move on any rebuild:

IDENTICAL  /docs/architecture              (54719 bytes)
IDENTICAL  /zh-Hans/docs/architecture      (54815 bytes)
IDENTICAL  /ja/docs/architecture           (54876 bytes)

The strip was verified to be doing work rather than comparing two empty files: the raw HTML contains self.__next_f, the stripped file contains none, and the stripped files are ~54 KB.

The two builds also produced an identical route table, and the same 310 pre-existing OG-card font-fetch warnings (a sandbox TLS artifact, unchanged by this diff).

Gates

Union re-run after the final commit, on 38e5a04:

  • pnpm turbo run type-check build test --forceTasks: 3 successful, 3 total
  • node .github/scripts/check-locale-surface.mjs — exit 0; "every advertised URL has a source file and every source file is advertised; both llms bodies carry every en-only page title and none from the other locales"

tsc was confirmed to actually include the edited file (--listFiles reports app/[lang]/docs/[[...slug]]/page.tsx), so "type-check clean" is a statement about this change and not a vacuous pass. Every exit code was captured before any pipe.


Generated by Claude Code

The Copy Markdown button and the Open popover both derived their target as
`page.url` plus `.mdx`. On a locale-prefixed route `page.url` carries the
prefix, and `next.config.mjs` rewrites exactly one shape — `/docs/:path*.mdx`
to `/llms.mdx/docs/:path*`. A prefixed URL matches no rewrite, falls through to
this route with `architecture.mdx` as a slug, and is answered by `/_not-found`
under `dynamicParams = false`. That was six of seven locales on all 79 pages.

Derive the URL from `page.slugs` instead, through the existing `docsPath`
helper, and bind it once so both controls take the same value. The prefix is
dropped rather than the rewrite widened because the machine-facing markdown
surface is English-only by AGENTS.md rule 1 — the same pin `llms.txt` and
`llms-full.txt` carry — and `/llms.mdx/docs/[[...slug]]` is English-only by
construction, calling `source.getPage(slug)` with no language. A prefixed
rewrite would not deliver translated markdown; it would reverse that policy
through a routing change.

Byte-identical on the default locale, where `page.url` has no prefix to carry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G5zjYc2BoFV2NjKBBapC7C
@hotlong
hotlong marked this pull request as ready for review August 26, 2026 04:56
@hotlong
hotlong merged commit d0ee357 into main Aug 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The .mdx copy/view links 404 on all six non-default locales — the rewrite only covers the unprefixed path

2 participants