Titles
exact duplicates, and titles that only differ by the site name once the common | Brand suffix or prefix is removed.
GitHub Packages · MIT · Node ≥ 18
Detect duplicate and near-duplicate titles, meta descriptions and page bodies across your built static site.
1 Point the @mrlouiswright scope at GitHub Packages in .npmrc
@mrlouiswright:registry=https://npm.pkg.github.com2 Install
npm i -D @mrlouiswright/duplicate-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 duplicate-beacon test/fixtures/badduplicate-beacon — /workspace/duplicate-beacon/test/fixtures/bad11 page(s); 11 compared (noindex and canonicalised pages excluded) HIGH b.html 2 pages share the title "Widgets | Acme": /a.html, /b.html [duplicate-title] ↳ fix: Give each page a unique, descriptive <title>.HIGH b.html 2 pages have identical main content: /a.html, /b.html [duplicate-body] ↳ fix: Consolidate them into one page, or point duplicates at the original with rel=canonical (or noindex them).MEDIUM b.html 2 pages share the meta description "Buy widgets online from Acme today at low prices.": /a.html, /b.html [duplicate-description] ↳ fix: Write a unique description for each page, or remove it and let search engines generate one.MEDIUM c.html /a.html and /c.html are 94% similar [near-duplicate-body]
Duplicate content splits ranking signals and makes search engines pick a URL for you. It usually comes from templates: every product page titled "Widgets | Acme", one meta description pasted into twenty pages, a print or AMP copy of every article, tag pages that are 95% the same. duplicate-beacon compares every built page with every other one, skipping pages that are noindex or already canonicalised elsewhere, and reports each group with the files, 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.
exact duplicates, and titles that only differ by the site name once the common | Brand suffix or prefix is removed.
exact duplicates, near-duplicates (template text with a word swapped), and descriptions that just repeat the title.
identical main content, and near-duplicates using 5-word shingles with MinHash/LSH candidate search and exact Jaccard similarity (--threshold, default 0.9). Navigation, header, footer and aside are left out of the main-content comparison.
pages whose text is mostly shared with many other pages (info).
# Example
npx duplicate-beacon dist# Example
npx duplicate-beacon dist --threshold 0.85 --fail-on medium# Example
npx duplicate-beacon public --ignore "/tags/**,/page/*" --min-words 80# Example
npx duplicate-beacon out --format json > duplicates.jsonpermissions:
contents: read
security-events: write # only needed for the SARIF upload
jobs:
duplicate-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/duplicate-beacon@v1
with:
path: dist
fail-on: high
sarif: duplicate-beacon.sarif
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: duplicate-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 | |
--threshold <0-1> | Similarity for near-duplicate bodies (default 0.9) |
--min-words <n> | Ignore bodies shorter than n words (default 50) |
--ignore <globs> | Pages to leave out of the comparison |
--no-template-check | Do not report boilerplate-heavy pages |
| Common | |
-c, --config <file> | JSON config (default: .duplicate-beacon.json or "duplicateBeacon" 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