🧠 HeyCMO

Lighthouse SEO Lint

How the SEO ≥ 0.95 gate works in CI, and how to run it locally.

Lighthouse SEO Lint

Heycmo gates merges to main on a Lighthouse SEO score of at least 0.95 for the public marketing pages. If a change drops SEO below 0.95 on any audited URL, CI fails and the PR cannot be merged.

This catches the regressions the team actually ships:

  • Missing or duplicated <title> / <meta name="description">
  • Broken rel="canonical"
  • Invalid JSON-LD structured data
  • Non-crawlable links (e.g. <a> without href)
  • Bad heading order
  • Blocked-from-index pages

How it works

The workflow lives at .github/workflows/lighthouse.yml and runs on every PR that touches apps/web/**.

  1. Builds the static site: npm --prefix apps/web run build
  2. Boots vite preview on 127.0.0.1:4173
  3. Runs @lhci/cli autorun against five representative URLs
  4. Asserts each category score against the thresholds in apps/web/lighthouserc.cjs
  5. Uploads the HTML + JSON reports as a build artifact

URLs audited

PathWhy
/Landing page
/toolsTools index
/tools/instagram-caption-generatorMost-trafficked free tool
/tools/ai-citation-scoreRecent GEO tool (sanity)
/built-by-heycmoPublic marketing page

/scan is not audited because it sits behind <RequireAuth> — a Lighthouse run would score the auth redirect, not the actual scanner.

Thresholds

CategoryThresholdSeverity
SEO>= 0.95error (blocks merge)
Accessibility>= 0.90warn (advisory)
Best Practices>= 0.90warn (advisory)
Performanceoffignored — too noisy in CI

Run it locally

cd apps/web
npm run lighthouse

This runs the same lhci autorun pipeline you'd see in CI: builds the site, serves it on 127.0.0.1:4173, audits all five URLs, and writes HTML reports to apps/web/.lighthouseci/lhr-*.html. Open them in your browser to inspect the failures.

Updating the audited URLs

Edit apps/web/lighthouserc.cjs and add the URL to the url array. Keep the list small (≤ 6 URLs) — each URL adds ~10s to CI runtime.

If you add a new high-traffic public page, audit it. If you add a gated route (<RequireAuth>), do not add it.

When the gate fails

  1. Open the failed CI run on GitHub.
  2. Download the lighthouse-reports build artifact.
  3. Open the relevant lhr-*.html file — it pinpoints the exact audit that failed.
  4. Fix and push.

If a legitimate score drop is unavoidable (e.g. a third-party embed), either fix the underlying issue or open a discussion before lowering the threshold in lighthouserc.cjs.

On this page