GitHub Packages · MIT · Node ≥ 18

canonical-beacon

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. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/canonical-beacon
Installing from GitHub Packages needs a token

Published 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.

~/project — zsh
$ 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]
Sample output from the canonical-beacon README.

Why it exists

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.

Features

Canonical tags

missing, more than one, empty or invalid, relative, outside <head>, tracking parameters or #fragment, og:url and Link header disagreements.

Canonical targets

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.

Mixed signals

a noindex page that also canonicalises to another URL.

hreflang codes

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.

hreflang clusters

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.

Usage

# 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.sarif

CI / GitHub Action

permissions:
  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 }

Options & flags

FlagDescription
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-urlsPages are served without .html (about.html -> /about)
Checks
--no-require-canonicalDo not report pages without rel=canonical
--allow-cross-domainAccept 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-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

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

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20