GitHub Packages · MIT · Node ≥ 18

heading-beacon

Audit the heading structure of your built static site: one visible H1 per page, a logical H1 → H2 → H3 outline, and headings that actually say something.

Audit heading structure in your built static site: exactly one visible H1, no skipped levels, no empty, generic, duplicate, image-only or overlong headings, H1 vs title agreement and site-wide duplicate H1s. Text/JSON/SARIF/Markdown, allowlist with expiry, GitHub Action. Zero deps.

  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/heading-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 heading-beacon test/fixtures/badheading-beacon — /workspace/heading-beacon/test/fixtures/bad7 page(s); 18 heading(s) HIGH     about.html /about.html has no <h1> [h1-missing]         ↳ fix: Add one <h1> describing the page (e.g. "About Acme").HIGH     index.html:1 Empty <h2> [heading-empty]         ↳ fix: Add text to the heading, or remove it if it is only for styling.HIGH     index.html:1 Empty <h2> (image without alt text) [heading-empty]         ↳ fix: Add text to the heading, or remove it if it is only for styling.MEDIUM   hidden.html:1 /hidden.html: the only <h1> ("Hidden page") is hidden [h1-hidden]         ↳ fix: Make the main heading visible; hidden headings carry little weight and confuse screen-reader users.MEDIUM   index.html:1 / has 2 <h1> elements: "Welcome", "Second main heading here" [h1-multiple]
Sample output from the heading-beacon README.

Why it exists

Headings are the outline search engines, answer engines and screen readers use to understand a page. Templates quietly break that outline: a logo wrapped in an “<h1>” adds a second H1, a card component jumps from “<h2>” to “<h4>”, a CMS leaves an empty “<h2>” behind, or every section is called "Overview". heading-beacon reads every built HTML page and reports the file, line, severity and a concrete fix for each problem, before you deploy.

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

H1

missing, more than one, hidden, very short, sharing no words with the <title>, and the same H1 on several pages.

Hierarchy

skipped levels (<h2> → <h4>), lower-level headings before the H1 in the main content (headings in <header>, <nav>, <aside> and <footer> are ignored), and long pages with no <h2>.

Quality

empty headings (including images without alt), image-only headings, generic or keyword-empty text ("Introduction", "Read more", "Section 3", lorem ipsum, plus your own list), duplicates at the same level, and overlong headings.

h1-missing

Page has no visible <h1> (default: high)

h1-multiple

Page has more than one <h1> (default: medium)

heading-empty

Heading has no text (and no image alt text) (default: high)

heading-level-skip

Heading skips a level (e.g. <h2> followed by <h4>) (default: medium)

heading-before-h1

A lower-level heading appears before the <h1> (default: low)

Usage

# Example

 npx heading-beacon dist

# Example

 npx heading-beacon dist --fail-on medium

# Example

 npx heading-beacon _site --max-length 90 --generic "Our services,Learn more"

# Example

 npx heading-beacon out --allow-multiple-h1 --format markdown > headings.md

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  heading-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/heading-beacon@v1
        with:
          path: dist
          fail-on: high
          sarif: heading-beacon.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: heading-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
--max-length <n>Report headings longer than n characters (default 120)
--long-page-words <n>Pages with more words than this need an <h2> (default 600)
--generic <words>Extra generic heading texts to flag (comma-separated)
--allow-multiple-h1Do not report pages with several <h1>
Common
-c, --config <file>JSON config (default: .heading-beacon.json or "headingBeacon" 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