Lighthouse CI: Catch Performance Regressions Early

Lighthouse CI fails your builds when performance drops. I run it on every pull request.

Lighthouse CI solves a specific problem: you fix performance issues, merge the PR, then someone adds a 500KB image next week and performance tanks again.

Lighthouse CI is a command-line tool that runs Google Lighthouse audits in your CI pipeline. It tests your site against performance budgets and fails the build if metrics drop below thresholds.

Update: Lighthouse CI 0.16 needs Node.js 22.19

@lhci/cli 0.16.0 (November 2, 2026) runs Lighthouse 13.6 and requires Node.js 22.19 or later. The CI examples below now use Node 22 and the GitLab image from the LHCI getting started guide.

Lighthouse 13 folded uses-responsive-images into image-delivery-insight and removed offscreen-images (details). The “Disabling audits” example now uses the new name.

I also corrected the mobile network profile: Lighthouse calls it Slow 4G, not slow 3G. See the v0.16.0 release notes.

Installation

Terminal window
npm install -g @lhci/cli@latest

Create lighthouserc.json in your project root. I’m using Astro, so the default port is 4321:

{
"ci": {
"collect": {
"url": [
"http://localhost:4321/",
"http://localhost:4321/blog"
],
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["error", {"minScore": 0.9}],
"first-contentful-paint": ["error", {"maxNumericValue": 2000}],
"largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
"cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}]
}
}
}
}

Running Locally First

Start by running Lighthouse CI locally. This lets you set realistic performance budgets before adding it to your pipeline.

Terminal window
npm run dev
lhci autorun

The autorun command runs three audits per URL, takes the median scores, and checks them against your assertions. When audits fail, you’ll see exactly which metrics are over budget.

This is where you tune your configuration. If your site consistently scores 0.85 on performance but you set minScore: 0.9, every build fails. Run locally, see what scores you actually get, then set budgets slightly above your current performance. The goal is to prevent regressions, not to fail every build.

For static sites, point directly to your build output:

{
"ci": {
"collect": {
"staticDistDir": "./dist"
}
}
}

For apps that need a running server, tell Lighthouse CI how to start it:

{
"ci": {
"collect": {
"url": ["http://localhost:3000/"],
"startServerCommand": "npm run preview"
}
}
}

Once your assertions pass locally, add Lighthouse CI to your pipeline.

Framework-Specific Setup

Next.js:

Next.js uses port 3000 by default. Your configuration needs npm run build to generate the production build, then npm start to serve it:

{
"ci": {
"collect": {
"url": ["http://localhost:3000/"],
"startServerCommand": "npm run build && npm start"
}
}
}

For static exports (output: 'export' in next.config.js), use staticDistDir:

{
"ci": {
"collect": {
"staticDistDir": "./out"
}
}
}

Nuxt:

Nuxt uses port 3000 by default. Build with npm run build, then preview with npm run preview (which uses nuxi preview internally):

{
"ci": {
"collect": {
"url": ["http://localhost:3000/"],
"startServerCommand": "npm run build && npm run preview"
}
}
}

For static generation (nuxi generate), point to the output directory:

{
"ci": {
"collect": {
"staticDistDir": "./.output/public"
}
}
}

Adding to CI

GitHub Actions (.github/workflows/lighthouse.yml):

name: Lighthouse CI
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Build site
run: npm run build
- name: Serve site
run: npm run preview &
- name: Wait for server
run: npx wait-on http://localhost:4321
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli@latest
lhci autorun

GitLab CI (.gitlab-ci.yml):

lighthouse:
image: cypress/browsers:node-22.21.0-chrome-141.0.7390.107-1-ff-144.0-edge-141.0.3537.92-1
script:
- npm ci
- npm run build
- npm run preview &
- npx wait-on http://localhost:4321
- npm install -g @lhci/cli@latest
- lhci autorun
only:
- merge_requests

Performance Budgets

The assertions block defines what passes and what fails.

Score-based budgets:

"categories:performance": ["error", {"minScore": 0.9}]

Performance score must be 90 or above. Lighthouse scores range from 0 to 1.

Metric-based budgets:

"first-contentful-paint": ["error", {"maxNumericValue": 2000}]

First Contentful Paint must be under 2000ms. This is more reliable than scores because it tests actual render timing.

Disabling audits:

"image-delivery-insight": "off"

Turn off audits that don’t apply to your use case. Responsive image warnings are often false positives for modern image formats.

What Happens When It Fails

Terminal window
Checking assertions against 2 URL(s), 3 run(s) each.
✘ http://localhost:4321/
categories:performance failure for minScore assertion
expected: >= 0.9
found: 0.87
largest-contentful-paint failure for maxNumericValue assertion
expected: <= 2500
found: 3200
Assertion failed. Exiting with status code 1.

The build fails. Fix the performance issue, push again, repeat.

Running Lighthouse CI with Desktop Settings

Lighthouse CI runs mobile audits by default. Mobile simulates a slower device with 4x CPU slowdown and Slow 4G network conditions. For desktop testing, configure the preset in your settings:

{
"ci": {
"collect": {
"settings": {
"preset": "desktop"
}
}
}
}

Desktop audits use faster throttling and no CPU slowdown. Test the device type your users actually use.

Storage Options

"upload": {
"target": "temporary-public-storage"
}

This stores reports at https://googlechrome.github.io/lighthouse-ci/viewer/ for 7 days. The URL appears in CI logs.

For permanent storage, use the Lighthouse CI Server or store results as build artifacts.

When It’s Most Valuable

Lighthouse CI catches regressions that code review misses. A developer adds moment.js (231KB). Code looks fine. CI fails because First Contentful Paint jumped from 1.8s to 3.2s.

Without CI, that ships to production. With CI, you catch it in the PR and suggest date-fns (13KB) instead.

It works best for content sites, marketing pages, and documentation. It’s less useful for dashboards and apps where performance varies based on data.


Trevor I. Lasn

Trevor I. Lasn

Building whiteheap.com and 0xinsider.com.



Related Articles

Check out these related articles that might be useful for you. They cover similar topics and provide additional insights.

Web performance
3 min read

Set loading=lazy on an iframe and the browser waits to load it until the reader scrolls near. Before-and-after code, the bandwidth saved and browser support.

Sep 13, 2024
Read article
Web performance
5 min read

Understanding the real use cases for WebAssembly beyond the performance hype

Nov 25, 2024
Read article
Web performance
4 min read

V8's compile hints let you control which JavaScript gets compiled immediately during page load

May 12, 2025
Read article
Web performance
9 min read

How the Speculation Rules API improves web performance by prefetching and prerendering future navigations

Sep 15, 2024
Read article
Web performance
3 min read

rel=modulepreload fetches, parses and compiles an ES module before a script imports it. How to use it, and why each module in the graph needs its own link.

Dec 4, 2024
Read article
Web performance
3 min read

A plain preload link fetches the wrong file for a responsive image. Add imagesrcset and imagesizes to link rel=preload so the browser picks the same image.

Nov 28, 2024
Read article
Web performance
4 min read

Using link rel='preconnect' can improve your website's performance by reducing connection setup times to key external domains.

Sep 13, 2024
Read article
Web performance
4 min read

Skeleton loading screens enhance user experience and make your app feel faster

Nov 12, 2020
Read article
Web performance
5 min read

From slow to fast: Using JavaScript's Performance API to optimize web apps

Nov 29, 2024
Read article

This article was originally published on https://www.trevorlasn.com/blog/lighthouse-ci. It was written by a human and polished using grammar tools for clarity.