Skip to content

Repository files navigation

seo-ci

Technical SEO is testable, so test it in CI and block the deploy, instead of discovering the regression weeks later as a ranking drop in Google Search Console.

seo-ci is a collection of reusable, composable GitHub Actions plus a reusable workflow that gate deployments on technical-SEO correctness. Each action is a single, sharp deploy gate: it exits non-zero with a clear, grep-able report when an SEO regression would ship.

robots.txt that de-indexes the site · sitemaps full of 404s and noindex URLs ·
canonical conflicts · 3-hop redirect chains · invalid JSON-LD · Core Web Vitals
drift · production serving something different from the build artifact

Note

Two audiences. If you own the deploy pipeline, jump to Quick start — wire in the reusable workflow or a single action. If you're an SEO specialist who wants to know exactly what each gate asserts, read The gates and each action's linked README.

The gates

Action What it catches
validate-robots The catch-all Disallow: / de-indexing catastrophe; legacy rules that block business-critical URLs; blocked CSS/JS; Googlebot crawl-delay. Wildcard-aware (*/$).
audit-sitemap Sitemap rot: URLs that 404, redirect, are noindex/robots-blocked, or canonicalize elsewhere; staging-host leaks; lying <lastmod>; 50k/50MB limits; malformed/XXE XML.
assert-canonicals Canonical present, singular, and consistent across the HTML <link> and the HTTP Link: header; points where you expect.
check-redirect-chains Multi-hop chains (httphttpswww→slash) that should be one hop; loops; broken termini. Reports the full chain.
validate-jsonld JSON-LD that isn't valid JSON, isn't a valid schema.org shape, or (consistency mode) describes data not on the page.
lighthouse-budgets LCP/INP/CLS/TTFB drift past budget, from a Lighthouse report or a fresh @lhci/cli run.
post-deploy-smoke Build-artifact-vs-production drift: a stripped header, wrong host routing, a noindex leaked from preview — checked against the live site after deploy.

Quick start

Option A — the reusable workflow (recommended)

Gate your pipeline in two phases around your deploy. In your repo's .github/workflows/deploy.yml:

jobs:
  pre-deploy:
    uses: nimajafari/seo-ci/.github/workflows/seo-gates.yml@v1
    with:
      run-post-deploy: false
      robots: ./public/robots.txt
      sitemap: ./public/sitemap.xml
      jsonld-urls: |
        ./public/products/widget/index.html
      lighthouse-report: ./lhr.json

  deploy:
    needs: pre-deploy
    runs-on: ubuntu-latest
    steps: [{ run: ./scripts/deploy.sh }]

  post-deploy:
    needs: deploy
    uses: nimajafari/seo-ci/.github/workflows/seo-gates.yml@v1
    with:
      run-pre-deploy: false
      base-url: https://www.example.com
      smoke-urls: |
        /
        /products/widget
      redirect-routes: |
        http://example.com/ -> https://www.example.com/

A complete, annotated version is in examples/consuming-workflow.yml.

Option B — a single action, standalone

Every action works in isolation:

- uses: nimajafari/seo-ci/actions/validate-robots@v1
  with:
    robots: https://www.example.com/robots.txt
    allowed: |
      https://www.example.com/products/widget
    blocked: |
      https://www.example.com/admin/

Per-action reference

Each action documents its own inputs, outputs, and finding codes:

Common outputs (every action): passed (true/false), errors, warnings, notices, and report — a JSON document { ok, strict, errors, warnings, notices, findings[] }. Every action accepts strict (treat warnings as errors) and report-path (write the JSON report to a file). Failures are emitted as GitHub annotations and as grep-able SEVERITY [CODE] message :: target lines, and the step exits non-zero.

Warning

These are starting points, not drop-in guarantees. Your URL patterns, canonical rules, and indexable set are specific to your site — tune each gate's inputs to match. A gate is only as good as the assertions you give it.

Don't cloak. Serving crawlers different content (robots.txt, canonicals, JSON-LD, status codes) than you serve users violates Google's policies. These gates check what a crawler sees; keep it the same as what users see.

How we know the gates actually work

A gate that can't fail is theater. So the gates are proven against fixtures:

  • fixtures/good/ — clean artifacts. The positive integration job runs every gate against them and asserts success.
  • fixtures/broken/ — the same artifacts with deliberately planted defects. The negative integration job runs every gate against them and asserts each one fails (via continue-on-error + an explicit "did it fail?" check). See fixtures/README.md for the defect-to-gate map.

Both jobs, plus unit tests, typecheck, lint, and a bundle-freshness check, run in .github/workflows/test.yml on every push and PR. No proprietary credentials are needed to run any of it.

Correctness notes (the pitfalls we avoid)

  • robots.txt wildcards. Python's urllib.robotparser does not support Google's */$ wildcards (it treats /*?*x= as a literal prefix and silently fails). We use the npm robots-parser; for the highest fidelity, wrap Google's C++ parser (google/robotstxt) — see validate-robots' README for the tradeoff.
  • XML hardening. Sitemap parsing refuses DOCTYPE/ENTITY declarations and disables entity processing, closing XXE/SSRF vectors; the 50 MB limit is checked against the uncompressed size.
  • No naive grep. The "no catch-all Disallow" check uses a real parser, because Disallow: / may live under a specific user-agent group rather than *.

Development

npm ci
npm run typecheck     # tsc --noEmit (strict)
npm run lint          # eslint
npm run test          # vitest unit + URL-mode tests
npm run build         # bundle each action to dist/index.js (committed)
npm run serve-fixtures  # serve fixtures at http://127.0.0.1:8099
npm run verify        # typecheck + lint + test + build

The dist/index.js bundles are committed (GitHub runs them directly); CI fails if they drift from source. Run npm run build and commit after changing an action.

Contributing

Issues and PRs welcome. Please keep the one-sharp-theme bar: every action is an SEO deploy gate, nothing else. A new gate needs action.yml, bundled dist/, src/ with the pure check logic factored out, unit tests, a good and a broken fixture, and a focused README. Run npm run verify before opening a PR.

License

MIT.

About

Composable GitHub Actions and a reusable workflow that gate deploys on technical-SEO correctness, robots.txt, sitemaps, canonicals, redirect chains, JSON-LD, and Core Web Vitals , so regressions fail CI instead of tanking rankings.

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages