All articles
Tutorial6 min read·May 31, 2026

How to Scan Your Website Before Every Deployment with GitHub Actions

Stop catching PageSpeed regressions, missing security headers, and broken SEO meta tags after deployment. Run an automated quality scan on every pull request — before anything hits production.

You deploy a new feature. Everything looks fine. Two days later someone notices the PageSpeed score dropped from 88 to 51. Or a new Content-Security-Policy header broke something so the team just... removed it. Or the staging environment had no SEO meta tags, and somehow the PR merged to production anyway.

These are the kinds of regressions that slip through every team's review process — not because developers are careless, but because nobody is checking the right things at the right moment. The right moment is before the PR merges, on the preview URL, as part of the normal CI workflow.

In this guide you'll set up an automated pre-deploy scan using SiteBrief and GitHub Actions. Every pull request gets a comment with PageSpeed score, security header grade, SSL status, and SEO meta check — before a single line ships to production.

What gets checked

Check
Pass
Fail / Warn
PageSpeed
Score ≥ 80
< 50 fails, 50–79 warns
Security headers
Grade A / A+ / B
Grade C warns, D/F fails
SSL
Valid certificate
Unreachable or error
SEO meta
Title + description present
Both missing = fail, one missing = warn

Step 1 — Get a SiteBrief API key

Create a free account at sitebrief.net/register, then go to Settings → API Keys and create a new key. It looks like sp_live_xxxxxxxx. You'll only see it once — save it.

Add it to your GitHub repo as a secret: Settings → Secrets and variables → Actions → New repository secret. Name it SITEBRIEF_API_KEY.

Step 2 — Add the workflow file

Create .github/workflows/sitebrief.yml in your repository. Pick the snippet that matches your deployment platform:

Vercel

name: SiteBrief Pre-deploy Scan

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  sitebrief:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write

    steps:
      - name: Wait for Vercel preview
        uses: patrickedqvist/wait-for-vercel-preview@v1.3.1
        id: vercel
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          max_timeout: 60

      - name: SiteBrief scan
        id: scan
        run: |
          RESULT=$(curl -s -X POST https://sitebrief.net/api/scan/preview \
            -H "Authorization: Bearer ${{ secrets.SITEBRIEF_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"url": "${{ steps.vercel.outputs.url }}"}')
          echo "markdown<<EOF" >> $GITHUB_OUTPUT
          echo "$RESULT" | jq -r '.markdown' >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Post PR comment
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          message: ${{ steps.scan.outputs.markdown }}

Netlify

name: SiteBrief Pre-deploy Scan

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  sitebrief:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write

    steps:
      - name: Wait for Netlify preview
        uses: probablyup/wait-for-netlify-action@3.2.0
        id: netlify
        with:
          site-id: ${{ secrets.NETLIFY_SITE_ID }}
          max-timeout: 60

      - name: SiteBrief scan
        id: scan
        run: |
          RESULT=$(curl -s -X POST https://sitebrief.net/api/scan/preview \
            -H "Authorization: Bearer ${{ secrets.SITEBRIEF_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"url": "${{ steps.netlify.outputs.deploy-url }}"}')
          echo "markdown<<EOF" >> $GITHUB_OUTPUT
          echo "$RESULT" | jq -r '.markdown' >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Post PR comment
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          message: ${{ steps.scan.outputs.markdown }}

Any other platform

If your CI already knows the preview URL — Render, Railway, Fly.io, a custom staging server — pass it directly:

- name: SiteBrief scan
  id: scan
  run: |
    RESULT=$(curl -s -X POST https://sitebrief.net/api/scan/preview \
      -H "Authorization: Bearer ${{ secrets.SITEBRIEF_API_KEY }}" \
      -H "Content-Type: application/json" \
      -d '{"url": "https://preview-${{ github.event.pull_request.number }}.staging.myapp.com"}')
    echo "markdown<<EOF" >> $GITHUB_OUTPUT
    echo "$RESULT" | jq -r '.markdown' >> $GITHUB_OUTPUT
    echo "EOF" >> $GITHUB_OUTPUT

Step 3 — What the PR comment looks like

After the first PR is opened, you'll see a comment like this automatically appended (and updated on every new push):

🔍 SiteBrief Pre-deploy Scan
URL: https://preview-42.myapp.com
Scanned: Sun, 31 May 2026 14:22:11 UTC
CheckStatusDetails
PageSpeed✅ passScore: 87/100
Security Headers⚠️ warnGrade C — missing: X-Frame-Options, CSP
SSL✅ passValid certificate
SEO Meta✅ pass✓ title, ✓ description
⚠️ Warnings — 3 passed, 1 warning, 0 failed

The comment is sticky — it updates in place on every new push. Your reviewers don't get a wall of duplicate comments as the PR evolves.

Optional: block merging on failures

By default the scan posts a comment but doesn't block the PR. If you want failures to block merging, add an exit code check and configure a required status check in your branch protection rules:

- name: SiteBrief scan
  id: scan
  run: |
    RESULT=$(curl -s -X POST https://sitebrief.net/api/scan/preview \
      -H "Authorization: Bearer ${{ secrets.SITEBRIEF_API_KEY }}" \
      -H "Content-Type: application/json" \
      -d '{"url": "${{ steps.vercel.outputs.url }}"}')

    echo "markdown<<EOF" >> $GITHUB_OUTPUT
    echo "$RESULT" | jq -r '.markdown' >> $GITHUB_OUTPUT
    echo "EOF" >> $GITHUB_OUTPUT

    # Fail the step if any checks failed
    FAILED=$(echo "$RESULT" | jq -r '.failed')
    if [ "$FAILED" -gt "0" ]; then
      echo "❌ $FAILED check(s) failed — blocking merge"
      exit 1
    fi
Tip: Start without the hard block. Let the comment run for a sprint first so your team gets used to it — then add the exit code once everyone agrees on the thresholds.

Run only the checks you care about

The checks parameter lets you skip checks you don't need. For a marketing site that doesn't control server headers, you might only care about PageSpeed and SEO:

-d '{"url": "...", "checks": ["pagespeed", "seo"]}'

Available values: pagespeed, security, ssl, seo. Default is all four.

Why catch this before deployment?

Monitoring tools like SiteBrief are great at telling you when something is wrong in production. But there's a category of problem that monitoring can't easily reverse: the PageSpeed regression you didn't notice until three days after deployment. The security header that got removed during a config refactor. The SEO meta tags that someone deleted and nobody caught.

Pre-deploy scanning closes the loop. Monitoring watches what's live. The GitHub Actions scan watches what's about to go live. Together they cover the full deployment lifecycle.

  • Monitoring — alerts you when the live site goes down or degrades
  • Pre-deploy scan — blocks regressions before they ever reach the live site
  • DevLab — automatically generates and opens a PR to fix detected issues
Set it up in 5 minutes → Create a free SiteBrief account at sitebrief.net/register, grab your API key from Settings, add the workflow file, and your next PR will already have a scan comment.