Skip to content

🐛 Let --base decide where the built site lives - #1248

Merged
taras merged 1 commit into
v4from
tm/staticalize-base
Sep 27, 2026
Merged

taras merged 1 commit into
v4from
tm/staticalize-base

Conversation

@taras

@taras taras commented Sep 26, 2026

Copy link
Copy Markdown
Member

Motivation

llms.txt and feed.xml hardcoded https://frontside.com/effection, so every copy advertised production no matter where it was served from. Locally that meant llms.txt pointed agents away from the dev server they were reading it on; in a preview it pointed reviewers at production.

They hardcoded it because staticalize could not rewrite them: it only touched HTML, and --base dropped its path component, so a site published under /effection could not be expressed at all. staticalize 0.3.0 fixes both (#12, #13, #14).

Approach

  • llms.txt and feed.xml emit the origin that served them, through the useAbsoluteUrlFactory() the site already has. No new helper and no environment variable: the build rewrites them.
  • --base is now the only place a deployment says where it lives. Production passes https://frontside.com/effection, the canonical url, which 0.3.0 can express. A preview passes its own alias url, computed before the server starts — fork PRs deploy anonymously to a url nobody knows in advance, so they fall back to the netlify site.
  • Previews get their HTML fixed too. They have been building with --base=https://effection.netlify.app all along, so every rewritten absolute url in a preview pointed at production. Now a preview points at itself.
  • environment.url points at the alias deploy, so the link in the checks panel is the one the build's urls were written for. The per-commit snapshot url is still in the sticky comment.
  • The crawl passes --concurrency=75 --retries=3. It has to: see below.
  • The pagefind crawl stays on 0.2.7. Its base is its own origin, so it has nothing to rewrite, and pinning it forward would make the dev server wait out Deno's 24-hour minimum dependency age on the day of a release.

Verification

Ran the actual site with no SITE_URL of any kind and built it with the 0.3.0 binary and the production base — 513 pages, 21 assets:

llms.txt byte-identical to what production serves today
feed.xml <guid> values identical to the live feed, so no subscriber sees a re-flagged post
sitemap.xml <url> entries with canonical <loc>, valid schema at last
index.html og:image now https://frontside.com/effection/assets/… rather than the netlify host
everything no localhost left in any of the 528 files

Served without the build, llms.txt says http://localhost:8130/api/, which is the original complaint.

deno fmt --check (238 files), deno lint (198 files) and deno task test (37 passed, 241 steps) are clean.

A regression to be aware of

The v0.3.0 release binary rejects the arguments this workflow has always used:

staticalize-linux --site=… --output=… --base=…
→ retries: Invalid input: expected number, received undefined

v0.2.6 accepts the same command and applies the defaults its --help documents. Passing --concurrency=75 --retries=3 works around it, which is what this PR does; the flags can come out once the defaults are restored upstream.

Supersedes

Replaces #1246, which solved the same problem with a SITE_URL environment variable because staticalize could not. This is smaller — no helper, no env var, no dev-task wiring — and it fixes preview HTML, which #1246 did not.

@pkg-pr-new

pkg-pr-new Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/effection@1248

commit: 85804a6

@codspeed

codspeed Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 10.13%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 5 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
⚡ Memory effection-inline.recursion 5.4 KB 4.9 KB +10.13%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing tm/staticalize-base (85804a6) with v4 (61a2219)

Open in CodSpeed

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

🚀 Deploy Preview Ready!

llms.txt and feed.xml hardcoded https://frontside.com/effection, so every
copy advertised production no matter where it was served from: locally
llms.txt sent agents away from the dev server they were reading it on, and
a preview sent reviewers to production.

They now emit the origin that served them, and staticalize 0.3.0 rewrites
them on the way out — it honours the path of --base and rewrites textual
bodies, neither of which it could do before. That makes --base the only
place a deployment says where it lives: the canonical url in production,
and its own alias url in a preview, which also settles the html, since
previews have been rewriting theirs to production all along.

The crawl now passes --concurrency and --retries explicitly. 0.3.0 rejects
the invocation without them, where 0.2.6 applied its documented defaults.

The pagefind crawl stays on 0.2.7: its base is its own origin, so it has
nothing to rewrite, and pinning it forward would make the dev server wait
out deno's minimum dependency age on the day of a release.
@taras
taras force-pushed the tm/staticalize-base branch from 334d3aa to 85804a6 Compare September 27, 2026 21:51
@taras
taras merged commit ff2be9a into v4 Sep 27, 2026
19 checks passed
@taras
taras deleted the tm/staticalize-base branch September 27, 2026 21:54
taras added a commit to thefrontside/interactors that referenced this pull request Sep 28, 2026
The deploy staticalized with `--base=https://interactors.netlify.app`, so
every page told Google the canonical copy was the Netlify one:

    frontside.com/interactors/docs/quick-start
      <link rel="canonical" href="https://interactors.netlify.app/docs/quick-start">

frontside.com proxies this site and serves the same pages, so the two are
duplicates of each other, and the canonical pointed at the copy nobody
links to. The site frontside.com serves was disclaiming its own content.

The base is now the url readers actually visit. `useAbsoluteUrl` already
builds the canonical from the crawl origin, so staticalize rewrites it
along with `og:url` and every internal link; 0.3.0 is what honours the path
in `--base`, which is why this rides with that upgrade rather than going
first.

thefrontside/effection#1248 did the same for Effection. Graphgen is still
to do, and emits no canonical at all.
taras added a commit that referenced this pull request Sep 28, 2026
#1248 pointed the production `--base` at frontside.com/effection to get the
canonical right. That moved every other url with it, including the sitemap,
and frontside.com prefixes `/effection` onto a proxied sitemap whose entries
now already carried it. 519 urls 404'd and its deploys have failed since.

staticalize 0.3.1 separates the two. `--base` is where the bytes are again,
so the Netlify copy is readable on its own and its sitemap says so;
`--canonical` is the address readers arrive at, and only the urls that name
the page follow it.

That makes the site's own copy of the idea redundant. `canonical()` had been
rebasing onto a configured origin, which is exactly what staticalize now
does, so it is gone along with the `base` field and the `--base` option —
which also ends this project having two different flags by that name. What
`app.html.tsx` needs is the url of the page being served, so that is what it
asks for: `currentUrl()`, a plain operation rather than a member of the api.
Calling it `canonical` would have named it after what the crawl does to it.

Dropping the member also drops the `Api<UrlApi>` annotation, which only
existed because a member reached back through the api for another.

Verified against the real site: canonical, og:url and both hreflang
alternates name frontside.com; og:image and the sitemap name Netlify; the
sitemap has no doubled paths; and llms.txt still names the published site,
which is what #1248 wanted.

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 that referenced this pull request Sep 28, 2026
#1248 pointed the production `--base` at frontside.com/effection to get the
canonical right. That moved every other url with it, including the sitemap,
and frontside.com prefixes `/effection` onto a proxied sitemap whose entries
now already carried it. 519 urls 404'd and its deploys have failed since.

staticalize 0.3.1 separates the two. `--base` is where the bytes are again,
so the Netlify copy is readable on its own and its sitemap says so;
`--canonical` is the address readers arrive at, and only the urls that name
the page follow it.

That makes the site's own copy of the idea redundant. `canonical()` had been
rebasing onto a configured origin, which is exactly what staticalize now
does, so it is gone along with the `base` field and the `--base` option —
which also ends this project having two different flags by that name. What
`app.html.tsx` needs is the url of the page being served, so that is what it
asks for: `currentUrl()`, a plain operation rather than a member of the api.
Calling it `canonical` would have named it after what the crawl does to it.

Dropping the member also drops the `Api<UrlApi>` annotation, which only
existed because a member reached back through the api for another.

Verified against the real site: canonical, og:url and both hreflang
alternates name frontside.com; og:image and the sitemap name Netlify; the
sitemap has no doubled paths; and llms.txt still names the published site,
which is what #1248 wanted.

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 that referenced this pull request Sep 30, 2026
* 🐛 Name the published site with --canonical instead of --base

#1248 pointed the production `--base` at frontside.com/effection to get the
canonical right. That moved every other url with it, including the sitemap,
and frontside.com prefixes `/effection` onto a proxied sitemap whose entries
now already carried it. 519 urls 404'd and its deploys have failed since.

staticalize 0.3.1 separates the two. `--base` is where the bytes are again,
so the Netlify copy is readable on its own and its sitemap says so;
`--canonical` is the address readers arrive at, and only the urls that name
the page follow it.

That makes the site's own copy of the idea redundant. `canonical()` had been
rebasing onto a configured origin, which is exactly what staticalize now
does, so it is gone along with the `base` field and the `--base` option —
which also ends this project having two different flags by that name. What
`app.html.tsx` needs is the url of the page being served, so that is what it
asks for: `currentUrl()`, a plain operation rather than a member of the api.
Calling it `canonical` would have named it after what the crawl does to it.

Dropping the member also drops the `Api<UrlApi>` annotation, which only
existed because a member reached back through the api for another.

Verified against the real site: canonical, og:url and both hreflang
alternates name frontside.com; og:image and the sitemap name Netlify; the
sitemap has no doubled paths; and llms.txt still names the published site,
which is what #1248 wanted.

0.3.1 also restored the default for `--retries`, so the flag 0.3.0 forced us
to pass goes away with it.

* 📦 Take staticalize from npm rather than jsr

Deno's minimum dependency age refuses a jsr version for its first 24 hours,
which is why the pagefind route sat on 0.2.7 while the deploy moved on: the
comment on that pin says pinning it forward would make the dev server wait
out the policy on the day of a release.

npm resolution is not subject to it, so both callers can name the version
the deploy uses. `npm:staticalize@0.3.1` is the same program — the package
publishes a `bin` rather than a `/cli` export, so the specifier loses its
subpath.

Verified both callers against the running site: the deno task and the exact
command the pagefind route execs each crawled 505 pages and 21 assets with
no errors.

---------

Co-authored-by: Taras Mankovski <74687+taras@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 85804a64 Deployed Sep 27, 2026 by taras via deploy-preview #1405
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.

2 participants