Add SEO Checks to Your CI/CD Pipeline
Every deployment is a chance to accidentally break SEO. A developer removes a canonical tag, a build step strips meta descriptions, a config change adds noindex to production — and you don't find out until rankings drop weeks later. The fix is treating SEO like any other quality gate: run checks on every deployment and fail the build if something critical breaks.
Why Do SEO Checks Belong in CI/CD?
SEO regressions are invisible at deploy time. Unlike a broken API endpoint or a failing unit test, a missing <title> tag or an accidental noindex directive has no immediate error signal — it silently degrades over days or weeks as search engines recrawl and update their indexes. By the time rankings drop, the causal commit is buried under dozens of subsequent changes.
CI/CD SEO checks create the same feedback loop you have for code quality: if a deployment introduces an SEO regression, the pipeline fails, the developer sees the issue immediately, and it never reaches production.
Which Checks Matter Most in a Pipeline?
Not all 54 audit checks are equally critical at deploy time. These are the high-priority checks that justify a build failure:
| Check | Why it matters | Failure threshold |
|---|---|---|
| Meta title | Missing or empty title tanks click-through rate | Must be present |
| Meta description | Direct impact on SERP snippet quality | Must be present |
| Canonical tag | Duplicate content and indexation issues | Must be present |
| Noindex directive | Accidentally blocking pages from Google | Must not be set on key pages |
| HTTPS | Required for ranking; marks page as insecure | Must pass |
| Overall score | Composite health indicator | Define your own threshold (e.g., ≥ 70) |
GitHub Actions Workflow
Add this workflow to .github/workflows/seo-check.yml:
name: SEO Quality Gate
on:
deployment_status:
push:
branches: [main]
jobs:
seo-audit:
runs-on: ubuntu-latest
if: github.event.deployment_status.state == 'success' || github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install SEO Score API SDK
run: pip install seoscoreapi
- name: Run SEO audit
env:
SEO_API_KEY: ${{ secrets.SEO_API_KEY }}
AUDIT_URL: "https://your-production-site.com"
SCORE_THRESHOLD: "70"
run: |
python - <<'EOF'
import os, sys
from seoscoreapi import audit
result = audit(os.environ["AUDIT_URL"], api_key=os.environ["SEO_API_KEY"])
score = result["score"]
grade = result["grade"]
threshold = int(os.environ["SCORE_THRESHOLD"])
print(f"SEO Score: {score}/100 ({grade})")
failures = []
checks = result.get("audit", {})
if checks.get("meta", {}).get("title") == "fail":
failures.append("CRITICAL: Missing meta title")
if checks.get("meta", {}).get("description") == "fail":
failures.append("CRITICAL: Missing meta description")
if checks.get("meta", {}).get("canonical") == "fail":
failures.append("CRITICAL: Missing canonical tag")
if score < threshold:
failures.append(f"Score {score} is below threshold {threshold}")
if failures:
print("\nSEO Quality Gate FAILED:")
for f in failures:
print(f" - {f}")
sys.exit(1)
else:
print("SEO Quality Gate PASSED")
EOF
Store your API key as a GitHub Actions secret: Settings > Secrets and variables > Actions > New repository secret named SEO_API_KEY.
How to Fail a Build If Score Drops Below a Threshold
The pattern above uses sys.exit(1) to signal failure to the CI runner. Choose your threshold based on your baseline:
- 70 — reasonable starting point for most sites
- 80 — appropriate for established sites where any regression is significant
- Custom — run an initial audit, note your current score, then set the threshold 5–10 points below it
To track score over time, write the result to a GitHub Actions output or post it as a PR comment using the GitHub API:
import os, json, urllib.request
score = result["score"]
comment = f"SEO Score: **{score}/100** ({result['grade']})"
pr_number = os.environ.get("PR_NUMBER")
if pr_number:
data = json.dumps({"body": comment}).encode()
req = urllib.request.Request(
f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/issues/{pr_number}/comments",
data=data,
headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}", "Content-Type": "application/json"}
)
urllib.request.urlopen(req)
GitLab CI Example
Add to .gitlab-ci.yml:
seo-audit:
stage: test
image: python:3.11-slim
script:
- pip install seoscoreapi
- |
python - <<'EOF'
import os, sys
from seoscoreapi import audit
result = audit("https://your-site.com", api_key=os.environ["SEO_API_KEY"])
print(f"Score: {result['score']}/100 ({result['grade']})")
if result["score"] < 70:
print(f"FAIL: Score {result['score']} is below threshold 70")
sys.exit(1)
print("PASS: SEO quality gate cleared")
EOF
variables:
SEO_API_KEY: $SEO_API_KEY
only:
- main
- merge_requests
Set SEO_API_KEY in Settings > CI/CD > Variables in your GitLab project.
Python Script for Custom Pipelines
For non-GitHub/GitLab pipelines (Jenkins, CircleCI, Bitbucket Pipelines, or a local pre-deploy hook):
#!/usr/bin/env python3
"""seo_gate.py — run before any deployment to catch SEO regressions."""
import sys
import os
from seoscoreapi import audit
AUDIT_URL = os.environ.get("AUDIT_URL", "https://your-site.com")
API_KEY = os.environ["SEO_API_KEY"]
THRESHOLD = int(os.environ.get("SCORE_THRESHOLD", "70"))
result = audit(AUDIT_URL, api_key=API_KEY)
score = result["score"]
grade = result["grade"]
priorities = result.get("priorities", [])
print(f"\nSEO Audit: {AUDIT_URL}")
print(f"Score: {score}/100 ({grade})")
critical_issues = [p for p in priorities if p["severity"] == "critical"]
if critical_issues:
print(f"\n{len(critical_issues)} critical issue(s) found:")
for issue in critical_issues:
print(f" - {issue['issue']}")
if score < THRESHOLD or critical_issues:
print(f"\nSEO Quality Gate: FAILED")
sys.exit(1)
print(f"\nSEO Quality Gate: PASSED")
sys.exit(0)
Run it as a pipeline step: SEO_API_KEY=your_key python seo_gate.py
Get Started
- Get a free API key — 5 audits/day, no credit card required
- Add the workflow YAML to your repo
- Set
SEO_API_KEYas a pipeline secret - Push to main and watch the quality gate run
For teams running audits on every PR across multiple environments, the Basic plan ($15/mo) gives you 1,000 audits/month at 30 RPM.
Frequently Asked Questions
Why should SEO checks run in CI/CD rather than on a schedule?
Scheduled audits tell you that something broke — CI/CD checks tell you which deployment broke it. Running checks at deploy time means you catch regressions in the same pull request that introduced them, before they ever reach production and before any search engine recrawls the affected pages.
Which SEO checks are most important to fail a build on?
Fail the build on missing meta title, missing meta description, missing canonical tag, and any noindex directive on pages that should be indexed. These are high-impact, unambiguous regressions. Use a score threshold (e.g., ≥ 70) as a secondary gate to catch broader degradation across multiple minor issues.
How do you set up the SEO audit GitHub Action?
Add a workflow file to .github/workflows/seo-check.yml with a Python step that installs seoscoreapi, runs audit() against your production URL, checks for critical failures, and calls sys.exit(1) if any are found. Store your API key as a GitHub Actions secret named SEO_API_KEY.
Does the SEO audit check a staging URL or the live site?
You can audit either. For catching regressions before they go live, point the audit at your staging or preview URL after deployment but before promoting to production. For production monitoring, audit the live URL as a post-deploy verification step.
How many API calls does a CI/CD pipeline use?
Each audit call counts as one request against your monthly quota. A team pushing to main once a day uses about 30 audits/month — well within the free tier. If you audit on every PR (e.g., 10 PRs/day), the Starter plan at $5/mo (200 audits/month) covers most teams comfortably.