Canonical tags
missing, more than one, empty or invalid, relative, outside <head>, tracking parameters or #fragment, og:url and Link header disagreements.
GitHub Packages · MIT · Node ≥ 18
Check canonical tags and hreflang clusters in your built static site: one canonical per page, pointing at a real, indexable, final URL, and hreflang clusters that are valid, self-referencing and reciprocal.
1 Point the @mrlouiswright scope at GitHub Packages in .npmrc
@mrlouiswright:registry=https://npm.pkg.github.com2 Install
npm i -D @mrlouiswright/canonical-beaconPublished to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.
$ npx canonical-beacon dist --base-url https://example.com --fail-on mediumcanonical-beacon — https://example.com11 page(s); 9 with canonical, 2 with hreflang at https://example.com HIGH a.html:1 /a.html declares 2 canonicals: https://example.com/a.html, https://example.com/b.html [canonical-multiple] ↳ fix: Keep exactly one rel=canonical; search engines may ignore all of them otherwise.HIGH c.html:1 Canonical https://example.com/d.html is noindex [canonical-target-noindex] ↳ fix: Canonicalise to an indexable page, or remove noindex from the target.HIGH e.html:1 /e.html declares 2 canonicals: https://example.com/gone.html, https://example.com/e.html [canonical-multiple] ↳ fix: Keep exactly one rel=canonical; search engines may ignore all of them otherwise.HIGH e.html:1 rel=canonical on line 1 is outside <head> [canonical-in-body] ↳ fix: Move it into <head>.HIGH e.html:1 Canonical https://example.com/gone.html has no page in the build [canonical-target-missing]
Canonical and hreflang mistakes are silent. A template ships two canonicals, a page canonicalises to a URL that was renamed, a translated page points hreflang at an English page that never links back, or a region is written en-UK. Search engines then ignore the hints, index the wrong URL or show the wrong language. canonical-beacon reads the built HTML (plus Link headers from _headers/vercel.json) and checks every canonical and hreflang annotation against the other pages in the build. Each finding has the file, line, severity and a concrete fix.
CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies. Works on any static build output (Astro, Next.js “output: 'export'”, Eleventy, Hugo, Jekyll, Docusaurus, VitePress, plain HTML) and runs offline: point it at the folder your build writes. By Louis Wright.
missing, more than one, empty or invalid, relative, outside <head>, tracking parameters or #fragment, og:url and Link header disagreements.
the target page must exist in the build, must not be a 404 page or meta-refresh stub, must not be noindex, and must not canonicalise somewhere else again (chains). Cross-host and http/https canonicals are flagged unless you allow cross-domain canonicals.
a noindex page that also canonicalises to another URL.
ISO 639-1 language with optional script and ISO 3166-1 / UN M.49 region (en-GB, zh-Hant-TW, es-419) or x-default; catches en-UK, en_GB and region-only codes.
self-reference, return links (reciprocity), duplicate codes, relative hrefs, alternates that are missing, noindex or non-canonical, hreflang on non-canonical pages, x-default presence, and <html lang> agreeing with the page’s own code.
# Example
npx canonical-beacon dist# Example
npx canonical-beacon dist --base-url https://example.com --fail-on medium# Example
npx canonical-beacon out --clean-urls --exclude "/drafts/**"# Example
npx canonical-beacon public --allow-cross-domain --format sarif > canonical.sarifpermissions:
contents: read
security-events: write # only needed for the SARIF upload
jobs:
canonical-beacon:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with: { persist-credentials: false }
- run: npm ci && npm run build # produce the static site first
- uses: MrLouisWright/canonical-beacon@v1
with:
path: dist
fail-on: high
sarif: canonical-beacon.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: canonical-beacon.sarif }
| Flag | Description |
|---|---|
| Site | |
--base-url <url> | Public URL of the site, e.g. https://example.com (default: inferred from canonical/og:url) |
--include <globs> | Only check these pages (file or URL path globs, comma-separated) |
--exclude <globs> | Skip these pages |
--clean-urls | Pages are served without .html (about.html -> /about) |
| Checks | |
--no-require-canonical | Do not report pages without rel=canonical |
--allow-cross-domain | Accept canonicals that point to another host |
| Common | |
-c, --config <file> | JSON config (default: .canonical-beacon.json or "canonicalBeacon" in package.json) |
-f, --format <fmt> | text (default) | json | sarif | markdown |
--json <file> | Also write a JSON report |
--sarif <file> | Also write SARIF 2.1.0 |
--markdown <file> | Also write Markdown (e.g. for a job summary) |
--fail-on <sev> | critical | high | medium | low | info | none (default: high) |
--list-rules | Print rules and default severities |
--no-fixes | Hide fix suggestions in text output |
--no-color | Disable colours |
Full reference, config file format and API in the README.
Support this project
I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.
Buy me a coffee (opens Stripe in a new tab)Secure checkout by Stripe