Skip to content

✨ Separate where a build is hosted from the url it is published at - #22

Merged
taras merged 1 commit into
mainfrom
canonical-url
Sep 28, 2026
Merged

taras merged 1 commit into
mainfrom
canonical-url

Conversation

@taras

@taras taras commented Sep 28, 2026

Copy link
Copy Markdown
Member

Closes #21.

Motivation

--base answers two questions at once:

  1. where these bytes live — what a[href], [src] and asset links point at
  2. what this content is called — what <link rel="canonical"> and
    <meta property="og:url"> claim

They coincide for an ordinary site, which is why nothing forced them apart. They
come apart the moment a site is served from one origin and read at another:
Effection is hosted at effection.netlify.app and published at
frontside.com/effection, and the same holds for Interactors and Graphgen.

Getting the canonical right meant pointing --base at the published url, which
dragged every other url along with it. thefrontside/effection#1248 did exactly
that, and the sitemap went with it — frontside.com mounts a proxied sitemap by
prefixing /effection onto every entry, the entries already carried it, and 519
urls 404'd:

Error: could not download http://127.0.0.1:8005/effection/effection/docs/resources
  Error: GET ... responded 404 Not Found

frontside.com's deploy has been failing since (thefrontside/frontside.com#509 is
the fix on that side). None of that was the intent of #1248; it followed from
--base meaning two things.

Approach

--canonical, defaulting to --base, so every existing invocation behaves
exactly as before. rebase() is then called with one of two targets depending
on what the url is for:

selector rebased onto
link[rel=canonical] canonical
meta[property=og:url] canonical
link[rel=alternate] canonical
a[href], [src], other link[href] base
textual bodies (llms.txt, feeds, …) canonical
the sitemap base

A preview can then say what it actually means:

--base=https://pr-333--interactors.netlify.app   # browsable on its own
--canonical=https://frontside.com/interactors    # index production, not me

Two calls worth reviewing, both argued in #21:

  • Textual bodies follow --canonical. llms.txt is a map telling an agent
    where the docs live rather than a page of the site, and #1248's intent was
    that production documents advertise production. The exception is a feed's
    rel="self", which should name where the feed is; a blind text substitution
    cannot tell the two apart.
  • The sitemap stays on --base. It maps this deployment, so a preview
    does not list production urls, and frontside.com's prefixing keeps working as
    it always did. Google's guidance that sitemaps list canonical urls argues the
    other way.

og:image and twitter:image follow --base — they point at bytes, and a
preview showing its own card seems right. Easy to move if you disagree.

canonical is optional in StaticalizeOptions too, so library callers are
unaffected.

Tests

Three new cases, and the existing 30 steps pass untouched — which is the point
of the default.

  • sends urls that name the page to --canonical, and the rest to --base —
    asserts canonical/og:url/alternate land on one, and stylesheet/script/
    anchor/og:image/sitemap on the other
  • names the published site in text bodies — llms.txt rewritten to the
    published site, asserting netlify appears nowhere
  • leaves everything on --base when --canonical is not given — the
    compatibility guarantee

I also ran the real CLI against the Interactors site:

staticalize --site http://127.0.0.1:8000 --output=… \
  --base=https://interactors.netlify.app \
  --canonical=https://frontside.com/interactors

canonical-bound: <meta property="og:url" content="https://frontside.com/interactors/docs/quick-start">
canonical-bound: <link rel="canonical" href="https://frontside.com/interactors/docs/quick-start">
base-bound:      <meta property="og:image" content="https://interactors.netlify.app/assets/images/meta-interactors.png">
base-bound:      <meta name="twitter:image" content="https://interactors.netlify.app/assets/images/meta-interactors.png">

sitemap: https://interactors.netlify.app/…
28 relative navigation links, untouched

What the consuming sites do next

`--base` answered two questions at once: where these bytes live, and what
this content is called. They coincide for an ordinary site and come apart
the moment one is served from one origin and read at another — Effection is
hosted at effection.netlify.app and published at frontside.com/effection.

Getting the canonical right meant pointing `--base` at the published url,
which dragged every other url along with it. thefrontside/effection#1248 did
exactly that, and the sitemap went with it: frontside.com prefixes `/effection`
onto a proxied sitemap, the entries already carried it, and 519 urls 404'd.

`--canonical` splits the two. Urls that name the page — a canonical link, the
`og:url` that says the same thing, an `alternate` saying it for another
language — rebase onto it. Navigation, assets and the sitemap stay on `--base`,
so the build still browses at the address it is served from.

Textual bodies follow `--canonical`: llms.txt is a map telling a reader where
the docs live, not a page of the site.

It defaults to `--base`, so every existing invocation is unchanged, and a site
hosted where it is published says so by saying nothing.

Closes #21.
@taras
taras merged commit d1f373c into main Sep 28, 2026
1 check passed
@taras
taras deleted the canonical-url branch September 28, 2026 20:26
taras added a commit to thefrontside/interactors that referenced this pull request Sep 28, 2026
Moving `--base` to frontside.com got the canonical right by moving every
other url with it: the sitemap, the social card, and any absolute link.
That is what broke frontside.com's deploy when Effection did the same, and
it left the Netlify build claiming to be somewhere it is not served from.

thefrontside/staticalize#22 split the two, released in 0.3.1. `--base` is
where the bytes are again, and `--canonical` is the address readers arrive
at, so a preview stays browsable on its own alias while still telling search
engines to index production.

0.3.1 also restored the default for `--retries`, so the flag 0.3.0 forced us
to pass goes away with it.
taras added a commit to thefrontside/graphgen that referenced this pull request Sep 28, 2026
Moving `--base` to frontside.com got the canonical right by moving every
other url with it, including the sitemap. That is what broke frontside.com's
deploy when Effection did the same, and it left the Netlify build claiming
to be somewhere it is not served from.

thefrontside/staticalize#22 split the two, released in 0.3.1. `--base` is
where the bytes are again, and `--canonical` is the address readers arrive
at. The canonical link added in the previous commit is what 0.3.1 rewrites;
without it there would be nothing here to name the published page.

0.3.1 also restored the default for `--retries`, so the flag 0.3.0 forced us
to pass goes away with it.
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.

✨ Separate where a build is hosted from the url it is canonically at

2 participants