Lighthouse CI and Performance Budgets: Catch Regressions Early

Crawvix Editorial Team · Published October 29, 2026 · Last updated October 29, 2026 · 10 min read

Sites rarely get slow in one big step. A new analytics tag, a larger hero image and an extra library each add a little, and nobody notices until the page feels heavy. Lighthouse CI runs Lighthouse automatically on every commit or pull request, compares the results with limits you chose, and can stop a release that crosses them.

This guide shows you how to set up Lighthouse CI, write a configuration file, add a performance budget for page weight and key metrics, and wire it into a build so a regression shows up before release instead of after. It is written for developers and technical site owners who already have a repository and a build pipeline, and for anyone who wants to understand what a performance budget is for.

When you finish you will have a working lhci autorun job, assertions that fail on the problems you care about, and a plan for tuning noisy results.

Before you begin

Lighthouse CI's own documentation lists what you need: source code managed with git, a CI system that gates branches or pull requests, a build that produces production assets, and either a command that starts a web server with production-like assets or a static site. It also needs Chrome available on the CI machine, which most CI images or setup steps provide.

Decide which pages to test. Pick a handful of representative URLs: the home page, a typical content page and one conversion page such as checkout or sign-up. Testing everything slows the pipeline and adds noise.

Record a baseline first. Run Lighthouse a few times by hand on those pages and note the results. A budget set with no baseline is a guess.

Know the limits. Lighthouse CI measures lab data, which comes from a controlled test run, not from your real visitors. A passing run does not mean real users see the same result, and it does not promise any ranking or score in search. Our guide on field data versus lab data explains why the two differ.

What a performance budget is

web.dev defines a performance budget as a set of limits on metrics that affect site performance, such as total page size, load time on a mobile network or the number of HTTP requests. It gives designers and developers a shared reference when they decide about features and technology.

The same article describes three kinds of metric. Quantity-based limits are counts and sizes, for example script size or the number of fonts. Milestone timings are events during page load, such as First Contentful Paint. Rule-based limits use scores from tools such as Lighthouse. It recommends combining quantity-based and user-centric metrics, because quantity alone can mislead: two pages with the same weight or request count can render differently, depending on load order.

web.dev suggests budgets per page type, for example a limit on JavaScript for product pages and a limit on image weight for search pages. It also suggests starting from research and testing rather than copying numbers. The article was last updated in 2018, and it says the figures it quotes are a starting point, so use your own measurements as the source of truth.

When a change crosses the budget, the article lists the choices: optimize an existing asset, remove one, or decide not to add the new feature.

Step-by-step instructions

Step 1: Install Lighthouse CI

Install the command line tool. The getting started documentation shows a global install pinned to a release line:

npm install -g @lhci/cli@0.15.x

In a project you can also add it as a development dependency and call it through npx. Check the project's README for the current release line and the Node versions it supports; the documentation's examples use Node 16.x, so confirm which Node versions the tool supports today and use a currently supported one.

What success looks like: lhci --version prints a version number.

Step 2: Create a configuration file

Create lighthouserc.js in the root of the repository. The docs describe three sections: collect (what to run Lighthouse on), assert (what must pass) and upload (where to send the reports).

module.exports = {
  ci: {
    collect: {
      startServerCommand: 'npm run start:production',
      url: [
        'http://localhost:3000/',
        'http://localhost:3000/blog/example-post',
        'http://localhost:3000/checkout',
      ],
      numberOfRuns: 3,
    },
    assert: {
      preset: 'lighthouse:recommended',
      assertions: {
        'uses-rel-preload': 'off',
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Notes on each part:

  • For a static site, use staticDistDir (for example './dist') instead of startServerCommand. For a server, startServerCommand starts it and url lists the pages to test.
  • numberOfRuns is how many times Lighthouse runs on each URL. The default is 3, and more runs smooth out noise at the price of time.
  • preset: 'lighthouse:recommended' is described in the docs as a realistic base: it requires perfect scores on audits outside performance and only warns on lower metric scores. The lighthouse:all preset asserts that every audit has a perfect score, and the docs advise it only for very high-quality new projects. A third preset, lighthouse:no-pwa, is the recommended one without the progressive web app audits.
  • Switching an audit to 'off' stops it from being checked. Use that for audits that do not fit your site, and write down why.
  • The temporary-public-storage target uploads results where anyone with the link can read them, and the reports are deleted after a few days, according to the docs. Do not upload reports for pages you cannot expose; use a private server instead.

What success looks like: The file is committed, and lhci autorun locally starts your server, tests the pages and prints assertion results.

Step 3: Choose assertion levels

Each assertion has a level. In the docs, off means the audit is not checked, warn means the result is printed but a failure does not cause a non-zero exit code, and error means failure exits with a non-zero code and so fails the CI job.

Start with warn for everything performance-related. Watch a few runs, learn what normal variation looks like, then move the checks you trust to error.

Assertions can check a score or a value:

assertions: {
  'categories:performance': ['warn', { minScore: 0.8 }],
  'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
  'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
}

minScore checks the audit score. maxNumericValue checks the raw value, in milliseconds for timings. By default, per the docs, multiple runs are combined with the optimistic method, The other options are median, pessimistic and median-run, so set aggregationMethod explicitly, for example to median, rather than relying on the default. Check the exact audit ids in your Lighthouse version, since names have changed between releases. Choose numbers from your baseline, not from this example. Our guides on Largest Contentful Paint and Cumulative Layout Shift explain what to aim for in each.

What success looks like: A deliberately tight limit in a test branch turns the job red, and you can read which assertion failed.

Step 4: Add size and request budgets

Page weight is more stable than timing, so it is a good first budget. The configuration docs give two ways.

Inside the assertions, using the form resource-summary:<resourceType>:(size|count). Sizes are in bytes:

assertions: {
  'resource-summary:script:size': ['error', { maxNumericValue: 300000 }],
  'resource-summary:image:size': ['warn', { maxNumericValue: 500000 }],
  'resource-summary:font:count': ['warn', { maxNumericValue: 4 }],
  'resource-summary:third-party:count': ['warn', { maxNumericValue: 8 }],
}

Or with a budget file, budgetsFile pointing at a budget.json. The docs say this option cannot be combined with any other assert option, and that the file's size unit is kilobytes whereas maxNumericValue for sizes is in bytes. That unit difference is an easy way to set a limit a thousand times too loose or tight, so check it.

Use different limits for different pages with assertMatrix, which applies assertions to URLs that match a pattern:

assert: {
  assertMatrix: [
    {
      matchingUrlPattern: '.*',
      assertions: {
        'resource-summary:script:size': ['warn', { maxNumericValue: 300000 }],
      },
    },
    {
      matchingUrlPattern: 'http://[^/]+/checkout',
      assertions: {
        'resource-summary:script:size': ['error', { maxNumericValue: 200000 }],
      },
    },
  ],
}

The numbers above are examples only. Set yours from the baseline you recorded, with a small margin. If your site ships a lot of JavaScript, see reducing JavaScript bundle size before you set a tight limit.

What success looks like: Adding an oversized script on a test branch fails the job with a message naming the resource summary assertion.

Step 5: Run it in CI

Add a job to your pipeline. The documentation's GitHub Actions example installs dependencies, builds, installs the CLI and runs lhci autorun:

name: CI
on: [push]
jobs:
  lhci:
    name: Lighthouse
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20.x
      - name: Install and build
        run: |
          npm ci
          npm run build
      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli@0.15.x
          lhci autorun

lhci autorun runs the collect, assert and upload steps for you and infers sensible defaults, and a failed assertion at the error level fails the build. The docs show an optional LHCI_GITHUB_APP_TOKEN secret that lets the tool post status checks on pull requests. The action versions and Node version above are examples; use versions your organisation supports. Never put secrets in the repository; keep tokens in your CI's secret store.

What success looks like: A pull request runs the job, and a regression turns the check red.

Step 6: Store history and tune for noise

Temporary storage is fine to start. For trends over time, the Lighthouse CI server stores historical results, shows them in a dashboard and offers a comparison view between builds. The docs warn that by default anyone with HTTP access can view and create data, and that its admin token should never be placed in the CI environment, so protect it.

Lab results vary from run to run, so expect some noise. Use more runs, the median aggregation method, test against a production-like build and avoid running other heavy jobs on the same machine. If a metric still jumps around, budget a stable one such as script size and watch timings as warnings.

Review the budget when the design changes on purpose. A budget that nobody can meet teaches people to ignore it.

What success looks like: The same unchanged commit passes repeatedly, and failures point to real changes.

Final verification checklist

  • lhci autorun works locally and in CI.
  • The pages tested represent your main templates.
  • Assertions are set from your measured baseline, not copied numbers.
  • Timing checks start as warn; checks you trust are error.
  • At least one size or request budget exists, with units checked (bytes in assertions, kilobytes in budget files).
  • A deliberately failing test branch proves the job can go red.
  • Reports are not uploaded to public storage if the pages are private.
  • No tokens or secrets are in the repository.
  • Someone owns the budget and reviews it when the design changes.

Common mistakes to avoid

  • Copying limits from an example. They do not fit your site. Measure first.
  • Mixing up units. Budget files use kilobytes; maxNumericValue for sizes uses bytes. Check each time.
  • Using lighthouse:all on an existing site. It asserts perfect scores on every audit and will fail for reasons that do not matter. Start with lighthouse:recommended.
  • Treating one run as truth. Lab results vary. Use several runs and a steadier aggregation method.
  • Uploading private pages to temporary public storage. Anyone with the link can read the report. Use a protected server.
  • Combining budgetsFile with other assert options. The docs say it cannot be used with them. Pick one approach.
  • Making every check an error on day one. Teams begin to bypass the job. Introduce errors gradually.
  • Testing a development server. Development builds are unminified. Test production-like assets.

Frequently asked questions

Is Lighthouse CI the same as running Lighthouse in Chrome?

It uses Lighthouse, but adds automation: it runs on each commit, repeats runs, applies assertions and can upload reports. The result is still lab data from one test environment.

Does it cost anything?

Your costs are the CI minutes the job uses and, if you run your own Lighthouse CI server, its hosting. The docs describe the temporary public storage option as the quickest setup at no cost, but its reports are readable by anyone with the link and deleted after a few days.

How many pages should I test?

Enough to cover your main templates, and few enough that the job stays quick. Three to five URLs is a reasonable start, adjusted to your site.

What should my first budget be?

Start with script size on your main templates, since it is stable and under your control. Add timings as warnings once you know their normal range.

Will passing the budget protect my search results?

No. It helps you notice slowdowns before release. Rankings depend on many factors, and real-user results may differ from lab ones.

Official references


Next step: See how Crawvix checks a website and capture a baseline once scanning opens. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.

In this category

All 25 Performance guides