Lighthouse score history without running an LHCI server
buildstats stores the Lighthouse scores and Core Web Vitals your CI measures, charts them per commit in your README and comments the delta on pull requests. Free for public repos; private $9/month per owner.
Lighthouse CI keeps history in its own server, which you host with a database, and reports to GitHub as a status check. If what you want is the score over time and a pull request comment that says the performance score dropped from 94 to 81, the server is a lot of machinery. The scores are numbers in a JSON file; pushing them takes one step.
Setup: run Lighthouse, push the scores
The example audits a preview URL with the Lighthouse CLI, which writes a JSON report; jq pulls the category scores and two Core Web Vitals out of it. Lighthouse CI's lhci collect writes the same report format to .lighthouseci/ if you already use it.
name: Lighthouse
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
id-token: write
pull-requests: write
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build && npx serve -s dist -l 3000 &
- run: npx lighthouse http://localhost:3000 --output=json --output-path=lh.json --chrome-flags="--headless" --quiet
- id: lh
run: |
echo "perf=$(jq '.categories.performance.score * 100' lh.json)" >> "$GITHUB_OUTPUT"
echo "a11y=$(jq '.categories.accessibility.score * 100' lh.json)" >> "$GITHUB_OUTPUT"
echo "seo=$(jq '.categories.seo.score * 100' lh.json)" >> "$GITHUB_OUTPUT"
echo "lcp=$(jq '.audits["largest-contentful-paint"].numericValue' lh.json)" >> "$GITHUB_OUTPUT"
echo "cls=$(jq '.audits["cumulative-layout-shift"].numericValue' lh.json)" >> "$GITHUB_OUTPUT"
- uses: bitgate/buildstats@v1
with:
metrics: |
lighthouse_performance=${{ steps.lh.outputs.perf }} higher
lighthouse_accessibility=${{ steps.lh.outputs.a11y }} higher
lighthouse_seo=${{ steps.lh.outputs.seo }} higher
lcp=${{ steps.lh.outputs.lcp }} ms lower
cls=${{ steps.lh.outputs.cls }} lower
max-regression: 5
Scores are pushed as 0 to 100 with the higher direction, so a drop reads as worse. LCP in milliseconds and CLS as a plain number use lower. Several pages push as variants: variant: home and variant: checkout store lighthouse_performance/home and lighthouse_performance/checkout on one chart. Lighthouse scores vary between runs; running the audit three times and pushing the median, which Lighthouse CI does with numberOfRuns, gives a steadier line.
The pull request comment
On pull_request runs the Action compares every metric against the latest value on the base branch and posts one comment with the base score, the new score and the delta. max-regression: 5 fails the step when a higher metric fell by more than 5% or a lower metric rose by more than 5%; the comment and the job summary are written first. Make the check required and a pull request that tanks the performance score cannot merge. Metrics without a base value never trip the gate.
The chart and the badge
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://buildstats.io/acme/site/lighthouse_performance.svg?theme=dark">
<img alt="Lighthouse performance score per commit" src="https://buildstats.io/acme/site/lighthouse_performance.svg">
</picture>

The chart draws up to 1,000 points, 400 to 1,200 pixels wide, in light and dark, and public images are cached for 5 minutes. The badge shows the latest score in the shields.io flat style, green when it improved against the previous commit and red when it fell.
Limits and pricing
| Public repositories | Private repositories | |
|---|---|---|
| Price | Free | $9/month per GitHub user or organization |
| Trial | Not needed | 14 days, no card |
| Metrics per project | 100 | 100 |
| Points per metric and branch | 50,000 | 50,000 |
| Metrics per push | 50 | 50 |
| Variants on one chart | 8 | 8 |
| Members | Unlimited | Unlimited |
Everything else is on the pricing page.
When Lighthouse CI is the better fit
Lighthouse CI (0.15.1) does three things buildstats does not: it asserts on any of the hundreds of individual audits with budgets in lighthouserc, it stores the full report per run so you can open the audit details for any commit, and its server compares two builds side by side. If reviewers need to know which audit failed, run LHCI. If the team needs the score trend in the README and a delta on the pull request, push the scores to buildstats; the two can run in the same job, with lhci collect writing the report and the Action pushing the numbers from it.
Questions
Which Lighthouse numbers should I track?
The four category scores and the metrics that move them: LCP, CLS, TBT and speed index. All of them are in the JSON report under categories and audits. A project holds 100 metrics.
How do I deal with score variance?
Run Lighthouse more than once and push the median; lhci collect does that with numberOfRuns. Keep max-regression wider than the run-to-run variance you see on the chart.
Can I track several pages?
Yes. Push each page as a variant, like lighthouse_performance/home and lighthouse_performance/pricing. They overlay on one chart and get separate rows in the pull request comment.
Does buildstats run Lighthouse?
No. Your workflow runs it against a preview URL or a local server, and the Action pushes the numbers from the report.
Can I fail a pull request that lowers the score?
Yes. Set max-regression on the step and require the check in branch protection. The step fails after writing the comment, so the scores are visible on the failed check.