buildstats.io

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>

![performance](https://buildstats.io/acme/site/lighthouse_performance/badge.svg)

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 repositoriesPrivate repositories
PriceFree$9/month per GitHub user or organization
TrialNot needed14 days, no card
Metrics per project100100
Points per metric and branch50,00050,000
Metrics per push5050
Variants on one chart88
MembersUnlimitedUnlimited

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.

Last updated 2026-10-07.