SEO Monitoring with GitHub Actions
SEO regressions are invisible until Google notices them. A developer removes a canonical tag, a CMS update strips title tags. Nobody sees it for weeks. This guide shows how to catch those regressions in CI, the same way you catch type errors.
The API flow
The SEOLint API fits into any CI step in three calls:
POST /api/v1/scanStart a scan, get back scanId and pollUrlGET pollUrlPoll every 3s until status is completeRead issues[]Each issue has severity: critical | warning | info
Minimal workflow
Add to .github/workflows/seo-check.yml. Set SITE_URL as a repository variable.
name: SEO Audit
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
seo-audit:
runs-on: ubuntu-latest
steps:
- name: Start scan
id: scan
run: |
RESPONSE=$(curl -s -X POST https://seolint.dev/api/v1/scan \
-H "Content-Type: application/json" \
-d '{"url": "${{ vars.SITE_URL }}"}')
echo "scan_id=$(echo $RESPONSE | jq -r '.scanId')" >> $GITHUB_OUTPUT
echo "poll_url=$(echo $RESPONSE | jq -r '.pollUrl')" >> $GITHUB_OUTPUT
- name: Wait for results
id: results
run: |
POLL_URL="${{ steps.scan.outputs.poll_url }}"
for i in $(seq 1 20); do
STATUS=$(curl -s "$POLL_URL" | jq -r '.status')
[ "$STATUS" = "complete" ] && break
sleep 3
done
- name: Fail on critical issues
run: |
CRITICAL=$(curl -s "${{ steps.scan.outputs.poll_url }}" \
| jq '[.issues[] | select(.severity == "critical")] | length')
if [ "$CRITICAL" -gt "0" ]; then
curl -s "${{ steps.scan.outputs.poll_url }}" \
| jq -r '.issues[] | select(.severity == "critical") | "❌ " + .title'
exit 1
fi
echo "✅ No critical SEO issues"What gets checked
| Check | Severity |
|---|---|
| Missing title tag | critical |
| Missing H1 | critical |
| Missing canonical URL | critical |
| LCP > 2.5s | critical |
| Images missing alt text | warning |
| Missing Open Graph tags | warning |
| Meta description > 160 chars | warning |
| Missing robots meta | info |
Auto-open a GitHub issue
Add this step after the failure check. It opens an issue with the full markdown report attached.
- name: Open issue with report
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
MARKDOWN=$(curl -s "${{ steps.scan.outputs.poll_url }}/markdown")
gh issue create \
--title "SEO regression: $(date +%Y-%m-%d)" \
--body "$MARKDOWN" \
--label "seo"The /markdown endpoint returns structured fix instructions. Paste the issue body into Claude or Cursor to apply the fixes automatically.
FAQ
Can I run SEO checks in GitHub Actions?
Yes. POST a URL to the SEOLint API, poll for results, and fail the workflow if critical issues are found. All from a standard GitHub Actions step.
What SEO issues does automated monitoring catch?
Missing title tags, missing H1, broken canonical URLs, images without alt text, missing Open Graph tags, and Core Web Vital regressions.
What plan do I need for API access?
The Monitor plan (€29/mo) includes full API access and 30 scans per month, enough for most teams running checks on every deploy.
Add SEO monitoring to your pipeline
Takes 5 minutes. Monitor plan required for API access.