How to Automate SEO Checks in Your CI/CD Pipeline
SEO regressions happen silently. A developer removes a meta description during a refactor. A template change breaks heading hierarchy. Someone deletes the OG image tag while cleaning up markup. None of these trigger test failures, so they ship to production unnoticed. Weeks later, traffic drops and nobody knows why. Here is how to catch every one of these issues automatically, before they ever reach your users.
The Problem: Silent SEO Regressions
Your test suite checks that buttons work, APIs return the right data, and pages render without errors. But it almost certainly does not check whether your pages are still optimized for search engines. That means any of these changes can ship without anyone noticing:
- A meta description gets deleted during a component refactor
- A template update duplicates the H1 tag or removes it entirely
- Someone removes the Open Graph tags while cleaning up the <head>
- A CMS migration breaks structured data (JSON-LD) across every page
- A new layout drops the canonical URL, causing duplicate content issues
- Image alt text disappears when a component library gets updated
- The mobile viewport meta tag gets removed during a responsive redesign
Each of these is invisible to your users, invisible to your QA team, and invisible to your existing test suite. But Google notices. And by the time you see the impact in Search Console, the damage has been compounding for days or weeks.
The fix is straightforward: automate SEO checks the same way you automate linting, type checking, and unit tests. Make them part of your pipeline. Fail the build when something breaks.
The Solution: SEOPeek in Your Pipeline
SEOPeek is an SEO audit API that runs 20 on-page checks on any URL and returns structured JSON. One GET request, one response, no browser automation or headless Chrome required. That makes it trivial to integrate into any CI/CD workflow.
The core idea is simple:
- Deploy your PR to a preview environment (Vercel preview, Firebase preview channel, Netlify deploy preview, etc.)
- Call SEOPeek with the preview URL
- Parse the JSON response and check the score
- If the score drops below your threshold, fail the build
The API response includes a numeric score (0–100), a letter grade (A–F), and individual pass/fail results for all 20 checks. You can gate on the overall score, or drill into specific checks that matter most to your site.
curl -s "https://seopeek.web.app/api/audit?url=https://your-preview-url.com" | jq '.score'
# Returns: 87
If that number drops from 87 to 52 because someone removed your meta tags, you want your pipeline to stop the deploy. Let us set that up.
Example: GitHub Actions Workflow
Here is a complete GitHub Actions workflow that runs programmatic SEO testing on every pull request. It assumes you have a preview deployment step that outputs a URL.
name: SEO Audit
on:
pull_request:
branches: [main]
jobs:
seo-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy Preview
id: deploy
run: |
# Replace with your actual preview deploy command
# This example uses Firebase hosting channels
PREVIEW_URL=$(firebase hosting:channel:deploy pr-${{ github.event.number }} \
--json | jq -r '.result."your-project".url')
echo "preview_url=$PREVIEW_URL" >> "$GITHUB_OUTPUT"
- name: Run SEO Audit
run: |
PREVIEW_URL="${{ steps.deploy.outputs.preview_url }}"
echo "Auditing: $PREVIEW_URL"
# Fetch the full audit response
RESPONSE=$(curl -s "https://seopeek.web.app/api/audit?url=$PREVIEW_URL")
# Extract score and grade
SCORE=$(echo "$RESPONSE" | jq '.score')
GRADE=$(echo "$RESPONSE" | jq -r '.grade')
echo "SEO Score: $SCORE ($GRADE)"
# List any failing checks
echo "$RESPONSE" | jq -r '
.checks | to_entries[] |
select(.value.pass == false) |
" FAIL: \(.key) — \(.value.message)"
'
# Fail if score is below threshold
if [ "$SCORE" -lt 70 ]; then
echo "::error::SEO score $SCORE is below the minimum threshold of 70."
exit 1
fi
echo "SEO check passed with score $SCORE ($GRADE)"
- name: Comment PR with Results
if: always()
uses: actions/github-script@v7
with:
script: |
const score = '${{ env.SEO_SCORE }}';
const grade = '${{ env.SEO_GRADE }}';
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `**SEO Audit Results**\n\nScore: ${score}/100 (${grade})\n\n_Powered by [SEOPeek](https://seopeek.web.app)_`
});
Tip: Set your threshold based on your current score. If your production site scores 85, set the threshold to 80. This catches real regressions without blocking PRs that make unrelated changes.
Example: Pre-Deploy Bash Script
Not every team uses GitHub Actions. If you deploy with a shell script, you can add an SEO automation API check in a few lines. This script audits multiple pages and fails if any of them drop below a minimum score.
#!/bin/bash
# seo-check.sh — Run before deploying to production
# Usage: ./seo-check.sh https://staging.yoursite.com
set -euo pipefail
BASE_URL="${1:?Usage: $0 }"
MIN_SCORE=70
FAILED=0
# List of critical pages to audit
PAGES=(
"/"
"/pricing"
"/docs"
"/blog"
"/about"
)
echo "Running SEO audit against $BASE_URL (minimum score: $MIN_SCORE)"
echo "---"
for page in "${PAGES[@]}"; do
URL="${BASE_URL}${page}"
RESPONSE=$(curl -s "https://seopeek.web.app/api/audit?url=${URL}")
SCORE=$(echo "$RESPONSE" | jq '.score')
GRADE=$(echo "$RESPONSE" | jq -r '.grade')
FAILS=$(echo "$RESPONSE" | jq '[.checks | to_entries[] | select(.value.pass == false)] | length')
if [ "$SCORE" -lt "$MIN_SCORE" ]; then
echo "FAIL $page — Score: $SCORE ($GRADE) — $FAILS checks failing"
# Print the failing checks
echo "$RESPONSE" | jq -r '
.checks | to_entries[] |
select(.value.pass == false) |
" \(.key): \(.value.message)"
'
FAILED=1
else
echo "PASS $page — Score: $SCORE ($GRADE)"
fi
done
echo "---"
if [ "$FAILED" -eq 1 ]; then
echo "SEO audit failed. Fix the issues above before deploying."
exit 1
fi
echo "All pages passed SEO audit. Safe to deploy."
Add this to your deploy pipeline by calling it before the actual deploy command:
# In your deploy script
./seo-check.sh "https://staging.yoursite.com" && firebase deploy --only hosting
If any page scores below 70, the deploy is blocked. The output tells you exactly which pages failed and which checks need attention.
What SEOPeek Checks
Every API call runs these 20 on-page SEO checks. Each one returns a pass/fail boolean, the extracted value, and a human-readable message explaining the result.
| # | Check | What It Catches |
|---|---|---|
| 1 | Page title | Missing, too short, or too long title tags |
| 2 | Meta description | Missing or poorly sized meta descriptions |
| 3 | H1 tag | Missing H1, multiple H1s, or empty H1 |
| 4 | Heading hierarchy | Skipped heading levels (H1 to H3 with no H2) |
| 5 | OG title | Missing Open Graph title for social sharing |
| 6 | OG description | Missing Open Graph description |
| 7 | OG image | Missing or broken Open Graph image |
| 8 | Twitter Card | Missing Twitter Card meta tags |
| 9 | Canonical URL | Missing or malformed canonical tag |
| 10 | Meta robots | Accidental noindex or nofollow directives |
| 11 | Structured data | Missing JSON-LD or invalid schema markup |
| 12 | Image alt text | Images without alt attributes |
| 13 | Mobile viewport | Missing or misconfigured viewport meta tag |
| 14 | Language attribute | Missing lang attribute on the html element |
| 15 | Character encoding | Missing or incorrect charset declaration |
| 16 | Favicon | Missing favicon link |
| 17 | Internal links | Pages with zero internal links (orphan pages) |
| 18 | External links | Link profile analysis |
| 19 | Word count | Thin content pages with insufficient text |
| 20 | HTTPS status | Pages served over insecure HTTP |
Every check maps to a concrete SEO issue that can silently regress when code changes. The structured JSON response makes it straightforward to parse in any language and integrate into any pipeline.
Pricing: Automated Audits vs. Manual Work
To put the cost in perspective, here is what SEO automation looks like with SEOPeek versus the alternatives:
| Approach | Cost | Time per Audit | Automation |
|---|---|---|---|
| SEOPeek API | Free – $29/mo | < 2 seconds | Full CI/CD |
| Manual audit (in-house) | $50–$150/hr | 15–30 min/page | None |
| SEO agency audit | $500–$5,000 | 1–2 weeks | None |
| Enterprise SEO platform | $99–$999/mo | 5–30 seconds | Partial (API) |
| Custom scripts (DIY) | Engineering time | Varies | Full (but fragile) |
A manual audit of 10 pages takes a skilled SEO specialist 3–5 hours. At $100/hour, that is $300–$500 every time you want to verify nothing is broken. And it only catches issues at the moment you run it. The next deploy could break everything again.
With SEOPeek in your SEO CI/CD pipeline, every pull request is automatically audited. The free tier covers 50 audits per day, which is enough for most teams. The Pro plan at $9/month gives you 1,000 audits per month. The Business plan at $29/month gives you 10,000.
The math: If you deploy 5 times per week and audit 5 pages each time, that is 100 audits per month. SEOPeek Pro ($9/mo) covers that with 900 audits to spare. A single manual audit costs more than an entire year of automated checks.
Advanced Patterns
Once you have the basics working, here are some patterns teams use to get more value from programmatic SEO testing:
Score Tracking Over Time
Log the score from every audit to a file or database. Plot it over time. You will see trends before they become problems.
# Append score to a log file after each deploy
SCORE=$(curl -s "https://seopeek.web.app/api/audit?url=https://yoursite.com" | jq '.score')
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ),$SCORE" >> seo-scores.csv
Per-Check Gating
Instead of gating on the overall score, fail the build if specific critical checks fail:
RESPONSE=$(curl -s "https://seopeek.web.app/api/audit?url=$URL")
# These checks must always pass
for check in title metaDescription h1 ogTags canonical; do
PASS=$(echo "$RESPONSE" | jq -r ".checks.$check.pass")
if [ "$PASS" != "true" ]; then
MSG=$(echo "$RESPONSE" | jq -r ".checks.$check.message")
echo "CRITICAL: $check failed — $MSG"
exit 1
fi
done
Slack or Teams Notifications
Post audit results to a Slack channel so the whole team sees SEO health:
SCORE=$(curl -s "https://seopeek.web.app/api/audit?url=$URL" | jq '.score')
GRADE=$(curl -s "https://seopeek.web.app/api/audit?url=$URL" | jq -r '.grade')
curl -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"text\": \"SEO Audit: $URL scored $SCORE/100 ($GRADE)\"}"
Multi-Page Sitemap Audit
Pull URLs from your sitemap and audit all of them:
# Extract URLs from sitemap and audit each one
curl -s "https://yoursite.com/sitemap.xml" \
| grep -oP '<loc>\K[^<]+' \
| while read -r url; do
SCORE=$(curl -s "https://seopeek.web.app/api/audit?url=$url" | jq '.score')
echo "$SCORE $url"
done | sort -n
Why Not Build Your Own?
You could write a custom script that parses HTML and checks for meta tags. Many teams have tried. Here is why it usually goes wrong:
- Scope creep: You start with title and meta description. Then you need OG tags, structured data, heading hierarchy, image alt text. Suddenly you are maintaining a 500-line script.
- Edge cases: Real-world HTML is messy. Self-closing tags, multiple charsets, conditional meta tags, JavaScript-rendered content. Your parser needs to handle all of it.
- Maintenance burden: SEO best practices evolve. Title length recommendations change. New meta tags become important. Someone has to keep updating your script.
- False positives: A brittle custom check that cries wolf on every PR gets disabled within a week. Getting the thresholds and logic right takes iteration.
SEOPeek handles all of this for you. Twenty checks, maintained and updated, accessible through a single API endpoint. You call it; you get a score. That is the entire integration.
Add SEO Checks to Your Pipeline Today
50 audits per day on the free tier. No signup, no API key, no configuration. One curl command and you are running automated SEO checks in under two minutes.
Try SEOPeek free →Conclusion
SEO regressions are a category of bugs. They are introduced by code changes, they affect users (through reduced discoverability), and they compound over time. The difference is that most teams have no automated checks for them.
Adding an SEO automation API like SEOPeek to your CI/CD pipeline takes less than ten minutes. You get 20 on-page checks on every pull request, a numeric score to gate deploys, and structured JSON you can pipe into any monitoring or alerting system. The free tier is enough for most teams. Paid plans start at $9/month.
Stop finding out about SEO regressions from your traffic dashboard weeks after the fact. Automate your SEO checks and catch them at the source: in the pull request that introduced them.