·5 min read

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:

  1. POST /api/v1/scanStart a scan, get back scanId and pollUrl
  2. GET pollUrlPoll every 3s until status is complete
  3. Read 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

CheckSeverity
Missing title tagcritical
Missing H1critical
Missing canonical URLcritical
LCP > 2.5scritical
Images missing alt textwarning
Missing Open Graph tagswarning
Meta description > 160 charswarning
Missing robots metainfo

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.