altdoc-multiversion-docs.yml deploys the same documentation to several paths (from its own header comment, lines 6-8):
/dev/ — default-branch docs
/latest-tag/ — current stable
/vX.Y.Z/ — one directory per release, kept as an archive
/pr-preview/pr<N>/ — per-PR previews
Every one of those serves near-identical HTML. The only <link rel="canonical"> this repo emits is on the root redirect stub, in generate_multiversion_landing_page.py:34 — a one-line page that immediately bounces to the target. Nothing inside the rendered version directories carries one.
So a search engine indexing one of these sites sees N copies of every page with no signal about which is authoritative. The usual outcomes are the archive or /dev/ outranking /latest-tag/, and a reader landing on documentation for a version they are not using.
The fix, and where the idea came from
IndrajeetPatil/workflows/.github/workflows/pkgdown.yaml (MIT) solves this for pkgdown with an inline R script that injects and then verifies a canonical tag in every generated page. Two things about it are worth copying beyond the idea itself:
- It fails loudly. It
stop()s if the pkgdown config is missing, if url is absent or not ^https://, if zero HTML files were produced, or if the insertion point (<title>, falling back to <head>) does not exist.
- It re-scans afterwards and asserts exactly one canonical tag per indexable page, aborting with the list of offending files.
404.html is excluded from the indexable set, and pages that already have a canonical are skipped rather than double-tagged.
That verification pass is the part that matters here: a wrong canonical is silent — the page still renders fine — so this is a check that has to be mechanical rather than eyeballed once and trusted.
Note the irony worth stating plainly: upstream needs this for a single-version site, where the duplicate-content problem is much milder than ours. We have four concurrent copies and no canonicals at all.
What to decide
- Target. Point every version directory's canonical at the
/latest-tag/ equivalent of the same page, so stable is authoritative. Pages that exist only in /dev/ (a newly documented function) have no /latest-tag/ counterpart — decide whether those self-canonicalize or are left untagged.
- PR previews. These should almost certainly be
noindex rather than canonicalized; worth handling in the same pass.
- Where it runs. Probably a new step in the composite family alongside
generate-altdoc-landing-page, reusing resolve-altdoc-base-url for the base URL rather than deriving it a second way.
- Whether Quarto can do it natively. Check
site-url handling before writing a post-processor — if Quarto emits canonicals from config, the fix may be configuration plus verification rather than HTML rewriting.
Source: IndrajeetPatil/workflows, MIT.
altdoc-multiversion-docs.ymldeploys the same documentation to several paths (from its own header comment, lines 6-8):/dev/— default-branch docs/latest-tag/— current stable/vX.Y.Z/— one directory per release, kept as an archive/pr-preview/pr<N>/— per-PR previewsEvery one of those serves near-identical HTML. The only
<link rel="canonical">this repo emits is on the root redirect stub, ingenerate_multiversion_landing_page.py:34— a one-line page that immediately bounces to the target. Nothing inside the rendered version directories carries one.So a search engine indexing one of these sites sees N copies of every page with no signal about which is authoritative. The usual outcomes are the archive or
/dev/outranking/latest-tag/, and a reader landing on documentation for a version they are not using.The fix, and where the idea came from
IndrajeetPatil/workflows/.github/workflows/pkgdown.yaml(MIT) solves this for pkgdown with an inline R script that injects and then verifies a canonical tag in every generated page. Two things about it are worth copying beyond the idea itself:stop()s if the pkgdown config is missing, ifurlis absent or not^https://, if zero HTML files were produced, or if the insertion point (<title>, falling back to<head>) does not exist.404.htmlis excluded from the indexable set, and pages that already have a canonical are skipped rather than double-tagged.That verification pass is the part that matters here: a wrong canonical is silent — the page still renders fine — so this is a check that has to be mechanical rather than eyeballed once and trusted.
Note the irony worth stating plainly: upstream needs this for a single-version site, where the duplicate-content problem is much milder than ours. We have four concurrent copies and no canonicals at all.
What to decide
/latest-tag/equivalent of the same page, so stable is authoritative. Pages that exist only in/dev/(a newly documented function) have no/latest-tag/counterpart — decide whether those self-canonicalize or are left untagged.noindexrather than canonicalized; worth handling in the same pass.generate-altdoc-landing-page, reusingresolve-altdoc-base-urlfor the base URL rather than deriving it a second way.site-urlhandling before writing a post-processor — if Quarto emits canonicals from config, the fix may be configuration plus verification rather than HTML rewriting.Source:
IndrajeetPatil/workflows, MIT.