Mark

Commercial performance monitoring starts at $29-99/month.
With Google PageSpeed API + GitHub Actions, you get continuous monitoring at $0.

The Need

After launching a site, you need to continuously monitor Core Web Vitals:

  • LCP (Largest Contentful Paint)
  • CLS (Cumulative Layout Shift)
  • INP (Interaction to Next Paint)

Commercial options: Speedcurve, Calibre, Sentry Performance — starting at $29-99/month.

Free option: Build it yourself.

Architecture

GitHub Actions (cron daily)
  → Call PageSpeed Insights API
    → Parse LCP/CLS/INP scores
      → Store as GitHub Artifact (historical comparison)
        → Score below threshold → Alert via PR/Issue

Enter fullscreen mode Exit fullscreen mode

PageSpeed API Free Tier

  • QPS: 20 requests/second
  • Daily quota: ~1,250 push triggers + daily cron is more than enough
  • API Key: Optional (higher limits with key)
  • Free tier: More than sufficient for personal projects

Implementation

Step 1: Base Workflow

# .github/workflows/pagespeed.yml
name: PageSpeed Insights

on:
  schedule:
    - cron: '0 0 * * *'  # Daily at UTC 00:00
  push:
    branches: [main]

jobs:
  pagespeed:
    runs-on: ubuntu-latest
    steps:
      - name: Run PageSpeed
        run: |
          URL="https://utlkit.com"
          API_KEY="${{ secrets.PAGESPEED_API_KEY }}"

          curl -s "https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${URL}&category=PERFORMANCE&category=ACCESSIBILITY&strategy=mobile${API_KEY:+&key=${API_KEY}}" \
            -o pagespeed-mobile.json

          curl -s "https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${URL}&category=PERFORMANCE&category=ACCESSIBILITY&strategy=desktop${API_KEY:+&key=${API_KEY}}" \
            -o pagespeed-desktop.json

      - name: Extract scores
        run: |
          echo "Mobile Performance: $(jq '.categories.performance.score' pagespeed-mobile.json)"
          echo "Mobile Accessibility: $(jq '.categories.accessibility.score' pagespeed-mobile.json)"
          echo "Desktop Performance: $(jq '.categories.performance.score' pagespeed-desktop.json)"

      - name: Upload results
        uses: actions/upload-artifact@v4
        with:
          name: pagespeed-${{ github.sha }}
          path: pagespeed-*.json
          retention-days: 90

Enter fullscreen mode Exit fullscreen mode

Step 2: Add Retry Logic

PageSpeed API occasionally returns 429 (rate limited) or 500:

MAX_RETRIES=3
RETRY_DELAY=5

for i in $(seq 1 $MAX_RETRIES); do
  HTTP_CODE=$(curl -s -o response.json -w "%{http_code}" \
    "https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${URL}&strategy=${STRATEGY}${API_KEY:+&key=${API_KEY}}")

  if [ "$HTTP_CODE" = "200" ]; then exit 0; fi

  if [ "$HTTP_CODE" = "429" ]; then
    echo "Rate limited, waiting ${RETRY_DELAY}s..."
    sleep $RETRY_DELAY
    RETRY_DELAY=$((RETRY_DELAY * 2))  # Exponential backoff
  else
    sleep $RETRY_DELAY
  fi
done

echo "Failed after $MAX_RETRIES retries"
exit 1

Enter fullscreen mode Exit fullscreen mode

Step 3: Add Alerts

LCP=$(jq '.audits.largest-contentful-paint.score' pagespeed-mobile.json)
CLS=$(jq '.audits.cumulative-layout-shift.score' pagespeed-mobile.json)
INP=$(jq '.audits.interaction-to-next-paint.score' pagespeed-mobile.json)

# Threshold alert (Google recommends: ≥90 good, ≥50 needs improvement)
if [ "$LCP" -lt 50 ] || [ "$CLS" -lt 50 ] || [ "$INP" -lt 50 ]; then
  echo "::warning title=Core Web Vitals below threshold::LCP=$LCP, CLS=$CLS, INP=$INP"
fi

Enter fullscreen mode Exit fullscreen mode

Step 4: CrUX Dashboard Integration

Chrome UX Report (CrUX) provides real user performance data:

This data is more authentic than PSI's lab data — it reflects global Chrome users' actual experience.

Real Debugging Case: CLS Spike

PageSpeed CI alert: CLS jumped from 0.01 to 0.25.

Debugging:

  1. PSI details → CLS contributor is the ad slot
  2. Ad slot loads dynamically with unknown height → layout shift
  3. Fix: Set aspect-ratio on ad container to reserve space
  4. Redeploy → CI runs PSI → CLS back to 0.01

Without CI, this regression wouldn't be found until users complained.

Cost Comparison

Option Monthly Cost Capability
Speedcurve $99+ Professional
Calibre $39+ Professional
PageSpeed CI (this approach) $0 More than enough

Lessons Learned

  1. Configure API Key — Free tier is enough, but a key is more stable
  2. Always add retries — PageSpeed API occasionally 429s
  3. Keep Artifacts for 90 days — Enough to track performance trends
  4. CrUX for real data — PSI is lab data; CrUX is real users
  5. Set alert threshold at 50 — Google's recommended line; too low = false alarms, too high = useless

Project

utlkit.com — 150+ free online tools