Skip to content

Give every blog post a canonical URL - #55

Merged
ralyodio merged 1 commit into
masterfrom
feat/blog-canonical-url
Sep 6, 2026
Merged

Give every blog post a canonical URL#55
ralyodio merged 1 commit into
masterfrom
feat/blog-canonical-url

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

The blog gets syndicated to dev.to and Hashnode, and those copies point
rel="canonical" back here. Until now the page they point at made no claim
about itself, so the original was the one page in the set staying quiet. This
fixes that end.

What changed

blog-post writes <link rel="canonical"> into each post's head. The URL is
the post's own, assembled from a new siteUrl config field at the one moment
both halves exist — inside createPost, which is where the file name is
decided. --canonical URL overrides it for a post whose original genuinely
lives elsewhere.

siteUrl is validated, not repaired

Anything that is not an absolute http(s) URL is dropped to null and the tag is
omitted: a bare host, a relative path, javascript:, ftp://, a non-string.
A canonical pointing somewhere wrong is worse than none, because search engines
act on it. Trailing slashes are trimmed so the joined URL has exactly one.

It defaults to null, and blog-post new mentions it once when it is unset
rather than inventing a URL for a blog it knows nothing about. A blog with no
siteUrl renders exactly the page it rendered before.

BLOG_SITE_URL overrides the file, matching the other config fields.

Verified

  • 711 tests pass (28 files), 10 of them new — covering the tag, HTML-escaping
    of the URL, self-canonical from siteUrl, explicit override, the no-config
    case, and every rejected URL shape
  • Ran blog-post new end to end against a scratch blog and read the output:
    <link rel="canonical" href="https://example.com/blog/001-post.html"> from a
    configured siteUrl of https://example.com/blog/

Version bumped to 0.28.0 (v0.27.0 is already tagged).

Note: src/free-names.ts has two pre-existing exactOptionalPropertyTypes
errors on master. I confirmed the file is byte-identical to origin/master and
left it alone rather than widening this diff.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GNpSGiTnfodDyCQ3MU6Nsk

These posts get syndicated. A readm3 release went to dev.to an hour ago with
--canonical-url pointing back at the blog, which is right, but the blog page
it points at made no claim about itself. The original was the one page in the
set staying quiet, which is the wrong way round: the copy asserts where the
original is and the original does not confirm it.

So `blog-post` now writes `<link rel="canonical">`. The URL is the post's own,
built from a new `siteUrl` config field once `createPost` knows the file name,
because that is the only point where both halves exist. `--canonical` overrides
it for the case where the original really is somewhere else.

`siteUrl` is validated rather than repaired: anything that is not an absolute
http(s) URL is dropped, and a trailing slash is trimmed. A canonical pointing
somewhere wrong is worse than no canonical at all, because search engines act
on it, so `javascript:`, a bare host and a relative path all resolve to null
and the tag is omitted. It is null by default, and `new` says so once rather
than guessing a URL for a blog it knows nothing about.

Nothing changes for a blog with no siteUrl configured: no tag, same page.

711 tests pass. `src/free-names.ts` has two pre-existing exactOptionalPropertyTypes
errors on master; they are untouched here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GNpSGiTnfodDyCQ3MU6Nsk
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

10 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 4 | LOW: 4

Severity Rule Location
HIGH sh-remote-script-execution root-ubuntu.sh:3004
HIGH sh-remote-script-execution root-ubuntu.sh:3008
MEDIUM sh-remote-script-execution root-ubuntu.sh:3157
MEDIUM sh-remote-script-execution root-ubuntu.sh:4186
MEDIUM redos-nested-quantifier src/domain-free.ts:56
MEDIUM redos-nested-quantifier src/mail.ts:1042
LOW secret-generic-credential src/credentials.ts:36
LOW secret-generic-api-key test/credentials.test.ts:208
LOW secret-generic-credential test/mail.test.ts:135
LOW secret-generic-credential test/shorten.test.ts:36

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 5b221a6 into master Sep 6, 2026
5 checks passed
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