From 278a698975311ac8af9d1c2b5a4e285dbae94ff2 Mon Sep 17 00:00:00 2001 From: Taras Mankovski Date: Mon, 28 Sep 2026 18:01:52 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Leave=20an=20alternate=20that=20?= =?UTF-8?q?is=20not=20a=20language=20variant=20on=20--base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `rel=alternate` covers two unrelated things. With `hreflang` it is this page in another language, which is a claim about the page and belongs with the canonical url. Without it — `type=application/rss+xml`, a `text/plain` llms.txt — it points at a separate file, and a file lives where the build is served. #22 sent both to `--canonical`, so a site that writes its feed link absolutely would have published a feed url on a site that does not host the feed. Nothing hit it: Effection writes those two relatively, and staticalize only rewrites urls that start with the crawl origin. --- README.md | 9 ++++++--- downloader.ts | 12 +++++++++++- test/staticalize.test.ts | 5 +++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6eb875d..3b5f6ef 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ two answers to what had been one question: where the bytes are, and what the content is called. `--canonical` separates them. Urls that _name the page_ — -``, its `` twin, and -`` — are rebased onto it, while navigation, assets and the -sitemap stay on `--base`: +``, its `` twin, and a +`` saying it for another language — are rebased +onto it, while navigation, assets and the sitemap stay on `--base`: ``` $ staticalize --site http://localhost:8000 --output dist \ @@ -54,6 +54,9 @@ The build still browses on its own at `--base`, and search engines are told to index `--canonical` instead. That is what a preview wants too: readable at its own alias, indexed as production. +An `alternate` without `hreflang` is left on `--base`: a feed or an `llms.txt` +is a separate file, and a file lives where the build is served. + Textual bodies follow `--canonical`, because a document like `llms.txt` is a map telling a reader where the docs live rather than a page of the site. diff --git a/downloader.ts b/downloader.ts index f76fdb9..824ad36 100644 --- a/downloader.ts +++ b/downloader.ts @@ -178,7 +178,17 @@ function names(element: { properties?: Record }): boolean { let rel = element.properties?.rel; if (Array.isArray(rel)) { - return rel.includes("canonical") || rel.includes("alternate"); + if (rel.includes("canonical")) { + return true; + } + + // `alternate` covers two unrelated things. With `hreflang` it is this page + // in another language, which is a claim about the page. Without it — a + // feed, an `llms.txt` — it points at a separate file, which lives wherever + // the build is served. + if (rel.includes("alternate")) { + return Boolean(element.properties?.hrefLang); + } } return element.properties?.property === "og:url"; diff --git a/test/staticalize.test.ts b/test/staticalize.test.ts index f0d1e45..f5f03d1 100644 --- a/test/staticalize.test.ts +++ b/test/staticalize.test.ts @@ -463,6 +463,7 @@ describe("staticalize", () => { + @@ -473,6 +474,7 @@ describe("staticalize", () => { `), ) .get("/about", (c) => c.html("

About

")) + .get("/feed.xml", (c) => c.text("")) .get("/card.png", (c) => c.text("")) .get("/styles.css", (c) => c.text("body {}")) .get("/main.js", (c) => c.text("console.log('hi')")) @@ -499,6 +501,9 @@ describe("staticalize", () => { ); // these point at bytes, which live where the build is hosted + expect(index).toContain( + ``, + ); expect(index).toContain( ``, );