CI integration
Last updated
Performance budgets in CI are how regressions get caught before users see them.
The shortest version is one line of the CLI: npx rankvitals test "$DEPLOY_URL" --budget-check "performance>=90" exits non-zero when the budget is missed. The workflow further down this page does the same thing with plain curl, for pipelines that would rather not add a dependency.
The RankVitals Action
Not published yet. A first-party GitHub Action is built and waiting on publication, so there is no uses: reference to copy today — until there is, the two recipes on this page are the supported way to gate a build, and both keep working afterwards. This section will carry the workflow snippet the day the action ships.
What it will do, so you can judge whether to wait for it: one step that starts the test, waits for it, enforces your budgets, and fails the job on a breach — no GitHub App to install and no checks: write permission, because a red check is just a non-zero exit code. Budgets are written the same way the CLI writes them (performance>=90,lcp<2500), a metric the run never measured counts as a breach rather than a silent pass, and the scores, Core Web Vitals, per-budget verdicts and a report link are written to the job summary. The commit SHA is the default idempotency key, so re-running a workflow replays the stored result instead of spending a credit.
Nothing is lost by not waiting: the CLI already has the same budget syntax and the same exit codes — 0 for a pass, 3 for a breached budget — so a workflow written around npx rankvitals today only needs the step swapped out later. The changelog is where the action will be announced.
Workflow with curl
Drop this into .github/workflows/perf.yml — it tests your deploy, waits for the result, and fails the job if the performance score drops below your budget. The commit SHA doubles as an idempotency key, so re-runs don't spend extra credits.
name: Performance budget
on:
deployment_status:
jobs:
lighthouse:
if: github.event.deployment_status.state == 'success'
runs-on: ubuntu-latest
env:
RANKVITALS_KEY: ${{ secrets.RANKVITALS_API_KEY }}
TARGET: ${{ github.event.deployment_status.target_url }}
BUDGET: 80 # minimum performance score
steps:
- name: Run test
run: |
TEST_ID=$(curl -s -X POST https://rankvitals.io/api/v1/tests \
-H "Authorization: Bearer $RANKVITALS_KEY" \
-H "Idempotency-Key: ${{ github.sha }}" \
-H "Content-Type: application/json" \
-d "{\"url\": \"$TARGET\", \"device\": \"mobile\"}" | jq -r .id)
echo "TEST_ID=$TEST_ID" >> $GITHUB_ENV
- name: Wait for result
run: |
for i in $(seq 1 40); do
BODY=$(curl -s https://rankvitals.io/api/v1/tests/$TEST_ID -H "Authorization: Bearer $RANKVITALS_KEY")
STATUS=$(echo "$BODY" | jq -r .status)
[ "$STATUS" = "completed" ] && { echo "$BODY" > result.json; exit 0; }
[ "$STATUS" = "failed" ] && { echo "Test failed"; exit 1; }
sleep 15
done
echo "Timed out"; exit 1
- name: Enforce budget
run: |
SCORE=$(jq -r .performance_score result.json)
GRADE=$(jq -r .grade result.json)
REPORT=$(jq -r .report_url result.json)
echo "Performance: $SCORE (grade $GRADE) — $REPORT"
echo "### ⚡ RankVitals: $SCORE/100 (grade $GRADE) — [full report]($REPORT)" >> $GITHUB_STEP_SUMMARY
[ "$SCORE" -ge "$BUDGET" ] || { echo "::error::Score $SCORE below budget $BUDGET"; exit 1; }Choosing a budget
Set the budget a few points below your current score rather than at your target — a gate that fails on normal run-to-run variance gets disabled within a week. Lighthouse scores move by a handful of points between identical runs, so leave headroom and tighten it as the number stabilises.
On a busy repository, prefer waiting on a callback to the polling loop above; see webhooks.