diff --git a/content/blog/CLAUDE.md b/content/blog/CLAUDE.md
index 93ec042ac..30f9ee90f 100644
--- a/content/blog/CLAUDE.md
+++ b/content/blog/CLAUDE.md
@@ -62,6 +62,7 @@ Freeform. Avoid duplicating `software` or `topics` values.
| `nohero` | Boolean, hides the hero image |
| `hidesubscription` | Boolean, hides the subscription CTA |
| `photo.url` / `photo.author` | Stock photo attribution |
+| `canonical_url` | Set only when another site's copy is the canonical version (e.g. the same post on `posit.co/blog`); the `` tag and JSON-LD then point there. Omit for the default: our page is canonical (self-referencing tag). |
| `code-line-numbers` | Boolean, turns on line-number gutters for every code block on the page |
## Project Blog Listing
diff --git a/layouts/partials/head.html b/layouts/partials/head.html
index efb953566..fc00edb47 100644
--- a/layouts/partials/head.html
+++ b/layouts/partials/head.html
@@ -9,6 +9,9 @@
content="{{ with .Params.Keywords }}{{ delimit . ", " }}{{ else }}{{ $.Site.Params.Keywords }}{{ end }}"
/>
+{{- /* Pages default to self-canonical; canonical_url in frontmatter defers to
+ another site's copy (e.g. posit.co/blog) */ -}}
+
{{- if $.Site.Params.pwa.enabled }}{{ end -}}
{{- /* Custom Open Graph and Twitter images for blog posts */ -}}
diff --git a/layouts/partials/jsonld.html b/layouts/partials/jsonld.html
index e3ecbdc40..c5b14e143 100644
--- a/layouts/partials/jsonld.html
+++ b/layouts/partials/jsonld.html
@@ -27,12 +27,13 @@
-}}
{{- else if and (eq .Section "blog") .IsPage -}}
+ {{- $canonical := or .Params.canonical_url .Permalink -}}
{{- $ld = dict
"@context" "https://schema.org"
"@type" "BlogPosting"
"headline" .Title
- "url" .Permalink
- "mainEntityOfPage" (dict "@type" "WebPage" "@id" .Permalink)
+ "url" $canonical
+ "mainEntityOfPage" (dict "@type" "WebPage" "@id" $canonical)
"datePublished" (.Date.Format "2006-01-02T15:04:05-07:00")
"dateModified" (.Lastmod.Format "2006-01-02T15:04:05-07:00")
"publisher" $org