A broken meta tag in one pull request can silently tank your search rankings for weeks. By the time you spot the traffic drop in Google Search Console, the damage is done. The fix is simple: treat SEO like a test suite and run it automatically on every PR.
This guide walks through a real, production-ready GitHub Actions workflow using the SEO Audit Action. By the end, you'll have a workflow that audits your deployed site, posts a score report as a PR comment, and fails the build if the score drops below your threshold.
What Does the GitHub Actions SEO Audit Do?
Here's what the workflow does on every pull request:
- Waits for your preview deployment (Vercel, Netlify, Cloudflare Pages, etc.)
- Runs a 28-check SEO audit against the preview URL
- Posts the score, grade, and top issues as a PR comment
- Blocks the merge if the score is below 80
The full audit covers meta tags, technical SEO, Open Graph, performance, and accessibility — the same checks you'd get from running curl https://seoscoreapi.com/audit?url=yoursite.com.
What Are the Prerequisites?
You need two things:
- A free SEO Score API key — sign up here (takes 10 seconds, no credit card)
- A preview deployment in your pipeline — any system that gives you a URL for the PR branch (Vercel, Netlify, Render, etc.)
Add your API key as a repository secret:
Settings > Secrets and variables > Actions > New repository secret
- Name:
SEO_SCORE_API_KEY - Value: your API key
How Do You Add It to a Workflow?
Start with the simplest version. Create .github/workflows/seo-audit.yml:
name: SEO Audit
on:
pull_request:
branches: [main]
jobs:
seo:
runs-on: ubuntu-latest
steps:
- name: SEO Audit
id: seo
uses: SeoScoreAPI/seo-audit-action@v1
with:
url: "https://your-site.com"
api-key: ${{ secrets.SEO_SCORE_API_KEY }}
threshold: 80
That's a working quality gate in 15 lines. If the SEO score is below 80, the workflow fails and the PR can't merge (assuming you have branch protection rules enabled).
The action produces a markdown summary in the GitHub Actions UI automatically — you'll see the score, grade, issue count, and top 5 issues right in the workflow run.
How Do You Get PR Comments on Audit Results?
The summary in the Actions tab is useful, but most reviewers look at the PR conversation, not the workflow logs. Let's post the results as a PR comment:
name: SEO Audit
on:
pull_request:
branches: [main]
jobs:
seo:
runs-on: ubuntu-latest
steps:
- name: SEO Audit
id: seo
uses: SeoScoreAPI/seo-audit-action@v1
with:
url: "https://your-site.com"
api-key: ${{ secrets.SEO_SCORE_API_KEY }}
threshold: 80
- name: Comment on PR
if: always()
uses: actions/github-script@v7
with:
script: |
const score = '${{ steps.seo.outputs.score }}';
const grade = '${{ steps.seo.outputs.grade }}';
const issues = '${{ steps.seo.outputs.issues }}';
const reportUrl = '${{ steps.seo.outputs.report-url }}';
const passed = parseInt(score) >= 80;
const body = [
`## SEO Audit Results`,
``,
`| Metric | Value |`,
`|--------|-------|`,
`| Score | **${score}/100** |`,
`| Grade | **${grade}** |`,
`| Issues | ${issues} |`,
`| Threshold | 80 |`,
`| Status | ${passed ? 'Passed' : 'Failed'} |`,
``,
`[View Full Report](${reportUrl})`,
].join('\n');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
The if: always() is important — it ensures the comment is posted even when the audit step fails due to a low score. Without it, a failing score would skip the comment step and you'd have to dig into the logs to find out what went wrong.
Real-World Example: Vercel Preview Deployments
Most teams don't audit a static production URL — they audit the preview deployment that Vercel (or Netlify, etc.) creates for each PR. Here's how to wire that up:
name: SEO Audit on Preview
on:
pull_request:
branches: [main]
paths:
- '**.html'
- '**.jsx'
- '**.tsx'
- '**.vue'
- '**.svelte'
- 'content/**'
- 'public/**'
jobs:
seo:
runs-on: ubuntu-latest
steps:
- name: Wait for Vercel Preview
uses: patrickedqvist/wait-for-vercel-preview@v1.3.2
id: preview
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 300
- name: SEO Audit
id: seo
uses: SeoScoreAPI/seo-audit-action@v1
with:
url: ${{ steps.preview.outputs.url }}
api-key: ${{ secrets.SEO_SCORE_API_KEY }}
threshold: 80
- name: Comment on PR
if: always()
uses: actions/github-script@v7
with:
script: |
const score = '${{ steps.seo.outputs.score }}';
const grade = '${{ steps.seo.outputs.grade }}';
const issues = '${{ steps.seo.outputs.issues }}';
const reportUrl = '${{ steps.seo.outputs.report-url }}';
const previewUrl = '${{ steps.preview.outputs.url }}';
const passed = parseInt(score) >= 80;
const emoji = passed ? '✅' : '❌';
const body = [
`## ${emoji} SEO Audit`,
``,
`Audited: ${previewUrl}`,
``,
`| Metric | Value |`,
`|--------|-------|`,
`| Score | **${score}/100** |`,
`| Grade | **${grade}** |`,
`| Issues | ${issues} |`,
`| Threshold | 80 |`,
``,
`[Full Report](${reportUrl})`,
].join('\n');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
Three things worth noting about this version:
Path filters — the paths block ensures the audit only runs when files that affect SEO are changed. Backend-only PRs (API routes, database migrations, config changes) skip the audit entirely. This is important because audits cost API calls, and you don't want to burn them on changes that can't affect your SEO score.
Preview URL — instead of hardcoding a URL, we pull it from the Vercel preview deployment. Every PR gets its own preview URL, so you're auditing the actual changes in that PR, not the current production site.
Timeout — max_timeout: 300 gives Vercel 5 minutes to deploy. Adjust this based on your build times.
Netlify Variant
If you're on Netlify instead of Vercel, swap the preview step:
- name: Wait for Netlify Preview
uses: jakepartusch/wait-for-netlify-action@v1.4
id: preview
with:
site_name: "your-netlify-site"
max_timeout: 300
The rest of the workflow stays identical — just reference ${{ steps.preview.outputs.url }}.
Multi-Page Audits
For sites where SEO matters on more than one page, use a matrix strategy to audit multiple routes:
jobs:
seo:
runs-on: ubuntu-latest
strategy:
matrix:
path: ['/', '/pricing', '/blog', '/docs']
steps:
- name: SEO Audit - ${{ matrix.path }}
id: seo
uses: SeoScoreAPI/seo-audit-action@v1
with:
url: "https://your-site.com${{ matrix.path }}"
api-key: ${{ secrets.SEO_SCORE_API_KEY }}
threshold: 80
This runs 4 parallel audits — one per page. If any page drops below 80, that specific job fails. You can see exactly which page regressed in the Actions UI.
For larger sites (10+ pages), use the batch audit endpoint with a script step instead of the matrix approach to use fewer API calls.
How Do You Set Up Branch Protection?
The workflow only blocks merges if you configure branch protection:
- Go to Settings > Branches > Branch protection rules
- Add a rule for
main - Check Require status checks to pass before merging
- Search for and add the SEO Audit check
Now any PR where the SEO score drops below your threshold literally cannot be merged until the issues are fixed.
Choosing Your Threshold
| Threshold | Best For |
|---|---|
| 90 | Marketing sites, landing pages where SEO directly drives revenue |
| 80 | Most production sites — catches real issues without false positives |
| 70 | Apps with some public-facing pages where SEO is secondary |
| 60 | Internal tools with minimal SEO requirements |
Start at 70 if your site has existing issues, then ratchet up to 80 once the team has fixed the backlog. Don't start at 90 — you'll frustrate developers with failures on day one and they'll disable the check.
Which Plans Support CI/CD Use?
The free tier gives you 5 audits/day, which works for solo projects with a couple PRs per week. For teams:
| Plan | Audits/mo | Price | Good For |
|---|---|---|---|
| Starter | 200 | $5/mo | Small team, 1-2 PRs/day |
| Basic | 1,000 | $15/mo | Active team, multi-page audits |
| Pro | 5,000 | $39/mo | Multiple repos, matrix audits |
All paid plans support CI/CD use. The free tier is sufficient for personal projects; Starter or above is recommended for active teams. Sign up free and upgrade when your PR volume outgrows the free tier.
The Complete Workflow
Here's the full production-ready workflow with all the pieces — preview deployment, PR comments, path filtering, and the quality gate:
name: SEO Quality Gate
on:
pull_request:
branches: [main]
paths:
- '**.html'
- '**.jsx'
- '**.tsx'
- '**.vue'
- '**.svelte'
- 'content/**'
- 'public/**'
jobs:
seo-audit:
runs-on: ubuntu-latest
steps:
- name: Wait for Preview Deploy
uses: patrickedqvist/wait-for-vercel-preview@v1.3.2
id: preview
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 300
- name: Run SEO Audit
id: seo
uses: SeoScoreAPI/seo-audit-action@v1
with:
url: ${{ steps.preview.outputs.url }}
api-key: ${{ secrets.SEO_SCORE_API_KEY }}
threshold: 80
- name: Post Results to PR
if: always()
uses: actions/github-script@v7
with:
script: |
const score = '${{ steps.seo.outputs.score }}';
const grade = '${{ steps.seo.outputs.grade }}';
const issues = '${{ steps.seo.outputs.issues }}';
const reportUrl = '${{ steps.seo.outputs.report-url }}';
const previewUrl = '${{ steps.preview.outputs.url }}';
const passed = parseInt(score) >= 80;
const emoji = passed ? '✅' : '❌';
const status = passed ? 'Passed' : 'Failed — fix issues before merging';
const body = [
`## ${emoji} SEO Audit`,
``,
`**${status}**`,
``,
`| Metric | Value |`,
`|--------|-------|`,
`| Preview | ${previewUrl} |`,
`| Score | **${score}/100** |`,
`| Grade | **${grade}** |`,
`| Issues | ${issues} |`,
`| Threshold | 80 |`,
``,
`[View Full Report →](${reportUrl})`,
].join('\n');
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
Copy that into .github/workflows/seo-audit.yml, add your SEO_SCORE_API_KEY secret, enable branch protection, and you have an automated SEO quality gate protecting your site from regressions on every pull request.
Frequently Asked Questions
What does the GitHub Actions SEO Audit action do?
The SEO Audit Action runs a 28-check SEO audit against your site on every pull request. It checks meta tags, technical SEO, Open Graph, performance, and accessibility, then posts a score and grade as a PR comment and fails the build if the score drops below your configured threshold.
How do you add the SEO audit to a GitHub Actions workflow?
Create .github/workflows/seo-audit.yml, add the SeoScoreAPI/seo-audit-action@v1 step with your url, api-key (stored as a repository secret), and a threshold value. The action installs automatically — no additional dependencies or setup scripts required.
What score threshold triggers a build failure?
The threshold is configurable via the threshold input parameter. The default recommendation is 80 for most production sites. Scores below your threshold fail the workflow step, which blocks the PR from merging when branch protection is enabled. Start at 70 for sites with existing issues, then raise it over time.
How do PR comments with audit results work?
Add a second step using actions/github-script@v7 with if: always(). It reads the score, grade, issues, and report-url outputs from the audit step and posts them as a formatted comment on the pull request. The if: always() ensures the comment appears even when the audit fails.
Which plans support CI/CD use with GitHub Actions?
All plans, including the free tier (5 audits/day), support CI/CD use. The free tier works for solo projects with a few PRs per week. For active teams, the Starter plan ($5/mo, 200 audits/month) or Basic plan ($15/mo, 1,000 audits/month) is recommended depending on PR volume and multi-page audit needs.