CI Build Gating & Deploy Markers
You can run a page speed monitor from your CI pipeline and fail the build when the page breaks a budget, so a slow release stops before it reaches your visitors. The same step can mark the deploy on the monitor's history charts, so a slowdown lines up with the release that caused it.
Everything runs through one small script served at https://speed.loadfocus.com/ci.js. It needs Node 14 or later and has no dependencies, so it works in GitHub Actions and in any other CI that runs Node.
Before you start
- A monitor for the page. The script runs a monitor you already have, see Getting started.
- The monitor id. Open a result of the monitor and scroll to CI and deploy markers under the history charts. The step under Fail a build when this page gets slower already contains the id, and Copy puts it on your clipboard. The id is also the part after
/speedtests/in the result page address. - An API key. Create one on the API Keys page and store it as a CI secret called
LOADFOCUS_API_KEY. If you work in more than one team, setLOADFOCUS_TEAM_IDtoo; without it your default team is used.
Each CI run starts a real run of the monitor, so it counts as one test run on your plan.
GitHub Actions example
Add your API key as a repository secret (Settings > Secrets and variables > Actions > LOADFOCUS_API_KEY), then create .github/workflows/page-speed.yml:
name: Page speed budgeton:push:branches: [ main ]jobs:page-speed:runs-on: ubuntu-lateststeps:- name: Page speed budgetrun: curl -fsSL https://speed.loadfocus.com/ci.js | node - --monitor YOUR_MONITOR_ID --marker "${{ github.sha }}" --budget "lcp<=2.5"env:LOADFOCUS_API_KEY: ${{ secrets.LOADFOCUS_API_KEY }}
Replace YOUR_MONITOR_ID with your monitor id. The step posts a deploy marker labelled with the commit, runs the monitor, waits for the result and fails the job when a budget breaks. Put it after your deploy step so it measures the release you just shipped.
To link the marker to the commit page, add --marker-url "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}". Repeat --budget for more limits, for example --budget "lcp<=2.5" --budget "performance>=90".
Options
| Option | What it does |
|---|---|
--monitor <id> | The monitor to run. Required. |
--budget "lcp<=2.5" | An extra limit for this run. Repeatable. |
--marker "<label>" | Also posts a deploy marker with this label (up to 120 characters), for example the commit or tag. |
--marker-url <url> | A link for the marker, for example the release or commit page. |
--timeout <seconds> | Gives up after this long. Default 900. |
--json | Prints the raw result as JSON. |
--host <url> | Default https://speed.loadfocus.com. |
curl -fsSL https://speed.loadfocus.com/ci.js | node - --help prints the same list.
Which budgets are checked
Every run is checked against two sets of limits:
- The monitor's threshold alerts. Each enabled alert with above or below and a value counts as a budget: an alert on Largest Contentful Paint above 2.5 becomes the budget "LCP at most 2.5 s". These are the same budgets the result page shows, see Performance budgets. Alerts that use "regresses by (%)" have no fixed limit and are not checked.
- Every
--budgetyou pass. A budget is a metric,<=or>=, and a number. You can pass up to 30.
| Metric | Unit |
|---|---|
performance, accessibility, best-practices, seo | score from 0 to 100 |
fcp, lcp, si, tbt, tti | seconds |
cls | Cumulative Layout Shift |
weight | page weight in KB |
requests | number of requests |
Time metrics are in seconds, Total Blocking Time included: tbt<=0.3 means 300 ms. A budget whose metric was not measured on the run fails, so a page that could not be measured never passes the gate.
Exit codes and output
| Exit code | Meaning |
|---|---|
| 0 | All budgets met. |
| 1 | A budget broke. |
| 2 | The run could not be checked: the API key or monitor id is missing, the run could not start (for example because of your plan's limits), the page could not be tested, the run was aborted, or there was no result before the timeout. |
The log shows one PASS or FAIL line per budget with the measured value, the limit and where the budget came from (alert or pipeline), then a link to the full report. If no budgets are set, the script says so and exits 0.
On GitHub Actions the result is also written to the job summary: the four Lighthouse scores, a table of every budget and a Full report link.
Deploy markers
A marker is a dashed line on the monitor's history charts, in the Deploys series. With --marker the script posts one before it starts the run. If the marker cannot be posted, the script says so and carries on with the check.
To mark deploys without running a check, use the command under Or only mark deploys in the same panel:
curl -X POST "https://speed.loadfocus.com/api/test/applighthouse/annotations" \-H "loadfocus-auth: YOUR_API_KEY" -H "content-type: application/json" \-d '{"label":"Deployed v1.4.2","configId":"YOUR_MONITOR_ID"}'
labelis required, up to 120 characters.- Leave out
configIdto mark every monitor in the team. - Add
urlto link the release. - Add
timestamp(ISO date or epoch) to back-date the marker, up to 30 days.
Recent markers in the panel lists the markers in the period shown, newest first. Markers posted without a monitor read all monitors, and Delete removes one. When a team reaches 5,000 markers, the oldest make room for new ones.
Compare two runs
When a budget breaks, the Compare runs section of the result shows what changed. Pick another run of the same monitor in Compare with and LoadFocus lists, side by side with a Change column:
- Scores and Metrics
- Page weight, in total and per resource type, plus the number of Requests
- New requests, Requests that grew and Removed requests
- New issues and Fixed since then, the audits that started or stopped failing
Add the other run's id to the result address as a compare parameter (for example &compare=12345 after the testrunid) and the page opens with that run already picked, which is handy to share.