Found while implementing #173. This is not a defect in #173's change — it is pre-existing on main, and #173 only measured it while proving its own output unchanged.
What was measured
Three production builds of apps/docs (pnpm run build), reading .next/server/app/sitemap.xml.body:
| build |
tree |
sha256 of raw bytes |
sha256 of sorted url blocks |
| A |
main @ 6653744 |
be9b8660... |
a57adad5... |
| B |
#173 branch @ 99e4815 |
d2d5c9a1... |
a57adad5... |
| C |
#173 branch @ 99e4815, rebuilt, tree untouched |
e1c28e8b... |
a57adad5... |
B and C are the same commit, same tree, two consecutive builds — and their raw sitemap bytes differ. All three agree exactly on the sorted set of 346 url entries.
So the sitemap's content is stable across builds; the order the entries are emitted in is not.
Where it comes from
sitemap.ts emits the static paths first, then source.getPages(i18n.defaultLanguage).map(...) in whatever order that call returns, and returns the concatenation unsorted. The entries that move between builds are the docs pages, and they move in contiguous chunks — docs/resources/changelog and docs/resources/faq with their locale siblings relocate as a block — which is the shape of an enumeration ordered by async file-read completion rather than by a sort.
Why it is worth filing now, not later
#175 is writing a gate that pins the generated sitemap. If that gate compares against a golden file, or asserts anything positional, it will flake: green or red depending on which build it happened to observe, on a tree nobody touched. A pin has to normalise — sort the url entries, or assert on a set — or it pins a coin flip.
The measurement above hands #175 the normalisation for free: sorting the url entries made all three builds agree byte-for-byte, on the same digest, across both trees.
Secondary: build output is not reproducible. deploy-docs.yml publishes on every push to main, so the served sitemap.xml changes bytes on deploys that changed no content.
Nothing user-visible is wrong today — entry order carries no meaning to a crawler. The entire cost is that anything asserting on, diffing, or caching these bytes is asserting on something that is not a function of the repository.
Suggested shape
Sort the entries in sitemap.ts before returning them (by url, or by priority then url), so the route becomes a pure function of the content. One line, in a file #175's gate is about to depend on.
Note this sits outside #175's declared file surface, which is the gate and no apps/docs/** source — which is why this is a separate card rather than a note on that one.
Reproduce
cd apps/docs
pnpm run build && sha256sum .next/server/app/sitemap.xml.body
pnpm run build && sha256sum .next/server/app/sitemap.xml.body # differs; tree untouched
Generated by Claude Code
Found while implementing #173. This is not a defect in #173's change — it is pre-existing on
main, and #173 only measured it while proving its own output unchanged.What was measured
Three production builds of
apps/docs(pnpm run build), reading.next/server/app/sitemap.xml.body:main@6653744be9b8660...a57adad5...99e4815d2d5c9a1...a57adad5...99e4815, rebuilt, tree untouchede1c28e8b...a57adad5...B and C are the same commit, same tree, two consecutive builds — and their raw sitemap bytes differ. All three agree exactly on the sorted set of 346 url entries.
So the sitemap's content is stable across builds; the order the entries are emitted in is not.
Where it comes from
sitemap.tsemits the static paths first, thensource.getPages(i18n.defaultLanguage).map(...)in whatever order that call returns, and returns the concatenation unsorted. The entries that move between builds are the docs pages, and they move in contiguous chunks —docs/resources/changeloganddocs/resources/faqwith their locale siblings relocate as a block — which is the shape of an enumeration ordered by async file-read completion rather than by a sort.Why it is worth filing now, not later
#175 is writing a gate that pins the generated sitemap. If that gate compares against a golden file, or asserts anything positional, it will flake: green or red depending on which build it happened to observe, on a tree nobody touched. A pin has to normalise — sort the url entries, or assert on a set — or it pins a coin flip.
The measurement above hands #175 the normalisation for free: sorting the url entries made all three builds agree byte-for-byte, on the same digest, across both trees.
Secondary: build output is not reproducible.
deploy-docs.ymlpublishes on every push tomain, so the servedsitemap.xmlchanges bytes on deploys that changed no content.Nothing user-visible is wrong today — entry order carries no meaning to a crawler. The entire cost is that anything asserting on, diffing, or caching these bytes is asserting on something that is not a function of the repository.
Suggested shape
Sort the entries in
sitemap.tsbefore returning them (by url, or by priority then url), so the route becomes a pure function of the content. One line, in a file #175's gate is about to depend on.Note this sits outside #175's declared file surface, which is the gate and no
apps/docs/**source — which is why this is a separate card rather than a note on that one.Reproduce
Generated by Claude Code