Closes #31.
Two thirds of this issue was already fixed, by a commit whose title gives no hint of it: 83e455f "feat: launch metadata, favicons, preview cards, privacy and terms" added public/robots.txt and public/sitemap.xml the day after the issue was filed. Searching the log for the feature name finds nothing; git log -S 'sitemap.xml' -- public finds it.
So this is the third part plus the things that keep the other two honest.
/404 answered 200
The body said Not found and the status line disagreed, so a crawler indexed every wrong URL as a real page and a client treated a miss as a hit. dashboard.stx already called notFound(); this page never did. One line.
The sitemap carried a date nothing updates
<lastmod>2026-09-29</lastmod> on all four entries. index.stx changed on 2026-09-30, so the / entry was wrong the day after it was written, and a date that lies is worse for a crawler than no date at all.
<priority> went with it: Google has ignored it since 2023 and the issue never asked for it.
The four <loc> values match the const path of the four indexable public views exactly, which I checked. /thanks and /checkout/<plan> are correctly absent, and robots.txt disallows both.
The deploy checks all three from outside
a missing page answers 404
/sitemap.xml answers 200
/robots.txt is the one in public/, not a CDN's managed replacementThat last one is the reason to check from outside at all. A CDN in front of the app can serve its own robots.txt and nothing in the repository would know, which was the issue's concern; its proposed mitigation was a one-time human look at the live bytes.
Two things I deliberately did not "fix"
The shipped robots.txt diverges from the issue's step 4, and is better for it. It disallows /checkout/, /thanks and /api/ rather than /dashboard. /dashboard already 404s in production and carries noindex, nofollow, whereas a crawler hitting /checkout/<plan> opens a real Stripe Checkout session on every request. I left it alone.
The acceptance criterion "exactly one <loc>" is now wrong and I did not enforce it. The same commit added /privacy and /terms, and /pricing already existed. The generalized form — every <loc> answers 200 — is what the deploy check enforces.
Verification
- the sitemap parses and its URLs match the views' declared paths
stx typecheckon404.stx: clean- 163 unit tests, lint and typecheck clean
The live behaviour of notFound() in 404.stx is proved by the deploy check on the next deploy, not here.
🤖 Generated with Claude Code