Skip to content

fix(site): canonicalise pages to the URL the site publishes - #211

Merged
vishr merged 1 commit into
mainfrom
fix/seo-canonical
Aug 30, 2026
Merged

fix(site): canonicalise pages to the URL the site publishes#211
vishr merged 1 commit into
mainfrom
fix/seo-canonical

Conversation

@vishr

@vishr vishr commented Aug 30, 2026

Copy link
Copy Markdown
Member

Starlight derives a page's canonical URL from the file it writes to disk.
Under build.format: "file" that file is start/install.html, so every
page shipped <link rel="canonical" href="https://fanout.run/start/install.html">
and an og:url to match.

Nothing else on the site uses that form. The sitemap lists
/start/install, every internal link points at /start/install,
llms.txt points at /start/install.md, and Cloudflare Pages — where the
site is published — serves the extensionless path and 301s the .html one
to it. The single tag whose job is to state the page's real address was
naming a redirect, on all 33 pages, and the sitemap and the canonicals
disagreed about which URLs exist.

Starlight's own formatCanonical returns the href untouched when the
build format is "file", which is right for a server that only serves
foo.html and wrong for this one, so the correction is a route middleware
rather than a configuration flag. It rewrites the two tags in place: a
second <link rel="canonical"> with a different href is worse than one
wrong href, because a crawler that sees the pair discards both.

scripts/check-canonical.mjs joins the build and asserts the three
properties that have to hold together — no .html in the canonical,
og:url agreeing with it, and the canonical present in the sitemap. The
regression it guards against produces a build that looks perfect and only
surfaces in a crawler weeks later.

https://claude.ai/code/session_01D39DJ5DrKHadUedNMSmAUn

Starlight derives a page's canonical URL from the file it writes to disk.
Under `build.format: "file"` that file is `start/install.html`, so every
page shipped `<link rel="canonical" href="https://fanout.run/start/install.html">`
and an `og:url` to match.

Nothing else on the site uses that form. The sitemap lists
`/start/install`, every internal link points at `/start/install`,
`llms.txt` points at `/start/install.md`, and Cloudflare Pages — where the
site is published — serves the extensionless path and 301s the `.html` one
to it. The single tag whose job is to state the page's real address was
naming a redirect, on all 33 pages, and the sitemap and the canonicals
disagreed about which URLs exist.

Starlight's own `formatCanonical` returns the href untouched when the
build format is "file", which is right for a server that only serves
`foo.html` and wrong for this one, so the correction is a route middleware
rather than a configuration flag. It rewrites the two tags in place: a
second `<link rel="canonical">` with a different href is worse than one
wrong href, because a crawler that sees the pair discards both.

`scripts/check-canonical.mjs` joins the build and asserts the three
properties that have to hold together — no `.html` in the canonical,
`og:url` agreeing with it, and the canonical present in the sitemap. The
regression it guards against produces a build that looks perfect and only
surfaces in a crawler weeks later.
@vishr
vishr merged commit bdf0aa5 into main Aug 30, 2026
8 checks passed
@vishr
vishr deleted the fix/seo-canonical branch August 30, 2026 15:48
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.

1 participant