

























Performance regressions are insidious. A single commit adds a 30 KB analytics script. Nobody notices in dev because the dev server runs on localhost with no throttling. The PR gets approved based on screenshots. Three weeks later, Lighthouse in production shows a 12-point drop in Performance score, and your mobile users have been bouncing off a page that takes 8 seconds to become interactive.
You cannot catch this with code review. You cannot catch it with unit tests. You can only catch it by running a real browser against your deployed page, measuring the same metrics that Google uses for ranking, and asserting that they stay above a hard threshold.
That is what Lighthouse CI gives you. A CI pipeline that runs Lighthouse, compares results against a defined budget, and fails the build when the site gets slower. No guesswork. No “we will fix it later.” No 3 a.m. alerts.
The Lighthouse Performance score is a weighted composite of six metrics: FCP, LCP, TBT, CLS, SI, and TTI. A single score is useful for a quick sanity check, but it masks regressions. You can drop 200 ms on LCP and lose 3 points, but if TBT improves by 50 ms you might net out at the same score. The PR passes, and your LCP has silently regressed.
A performance budget flips this. You set hard numeric thresholds for each metric:
These do not average out. Each one is an absolute gate. Exceed any threshold and the build fails. The developer who added the 30 KB script gets a red checkmark and a link to the Lighthouse report showing exactly which asset pushed them over.
Lighthouse CI (@lhci/cli) runs Lighthouse headlessly and exposes an assert API for budgets. You install it in your project, configure a budget file, wire it into your CI workflow, and you are done.
npm install --save-dev @lhci/cli
Create a .lighthouserc.js config file at the project root:
module.exports = {
ci: {
collect: {
// Run against the production build served locally
staticDistDir: './dist',
// Or against a deployed URL:
// url: ['https://staging.example.com'],
numberOfRuns: 3,
settings: {
preset: 'desktop',
// For mobile budgets, use:
// preset: 'desktop' with custom throttling, or the mobile preset
},
},
assert: {
assertions: {
// Performance score floor
'categories:performance': ['error', { minScore: 0.9 }],
// Metric budgets (mobile-emulated)
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'total-blocking-time': ['error', { maxNumericValue: 200 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'first-contentful-paint': ['error', { maxNumericValue: 1800 }],
'interactive': ['error', { maxNumericValue: 3500 }],
'speed-index': ['error', { maxNumericValue: 3000 }],
// Resource size budgets
'resource-summary:script:transfer': ['error', { maxNumericValue: 250 }],
'resource-summary:stylesheet:transfer': ['error', { maxNumericValue: 50 }],
'resource-summary:total:transfer': ['error', { maxNumericValue: 600 }],
// Warn on common anti-patterns instead of failing
'unminified-javascript': ['warn'],
'unused-javascript': ['warn', { maxLength: 0 }],
'offscreen-images': ['warn'],
'uses-responsive-images': ['warn'],
'uses-http2': ['off'],
},
},
upload: {
target: 'temporary-public-storage',
},
},
};
Break down what is happening here.
The collect phase builds your site or starts a server and runs Lighthouse against it. numberOfRuns: 3 means Lighthouse runs three times and takes the median, which smooths out variance from the CI runner’s background noise.
The assert phase is where the budget lives. Each assertion has a severity (error fails the build, warn prints a warning) and a threshold. The maxNumericValue is the hard cap for the metric in milliseconds or kilobytes.
The upload phase sends the results somewhere so you can view the full Lighthouse report. temporary-public-storage stores the report on a public URL that expires after 7 days. In production you would point this at your own storage or use the LHCI server for historical comparisons.
Here is the full workflow that builds your site, runs Lighthouse CI, and posts the results as a PR comment.
Create .github/workflows/lighthouse.yml:
name: Lighthouse CI
on:
pull_request:
branches: [main]
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lighthouse:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build the site
run: npm run build
- name: Run Lighthouse CI
run: |
npx lhci autorun --config=./lighthouserc.js
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
- name: Save Lighthouse report
if: always()
uses: actions/upload-artifact@v4
with:
name: lighthouse-report
path: .lighthouseci/
retention-days: 14
The LHCI_GITHUB_APP_TOKEN secret is optional but recommended. Without it, LHCI cannot post status checks and detailed reports to your PRs. To get the token, install the Lighthouse CI GitHub app on your repo. If you skip the token, the workflow still runs and fails on budget violations, but the results live only in the workflow logs and the uploaded artifact.
The numbers in the example config are aggressive. They assume a well-optimized static site or SSR app. If your app starts above those thresholds, do not set the budgets to match your current numbers. That locks in mediocrity. Instead, set budgets that are 10% better than where you are today, and tighten them every sprint.
Here is a phased approach for a team that is still optimizing:
Phase 1 (month one): Set budgets to warn, not error. Get the team used to seeing Lighthouse output in CI without blocking deploys. Discuss the results in standup.
assertions: {
'largest-contentful-paint': ['warn', { maxNumericValue: 3500 }],
'total-blocking-time': ['warn', { maxNumericValue: 400 }],
}
Phase 2 (month two): Tighten to error levels that match your current median. Any regression from the baseline fails the build.
Phase 3 (month three): Set budgets that represent your target, not your baseline. Tighten monthly.
The mistake most teams make is skipping Phase 1. They set aggressive budgets on day one, the CI pipeline turns red on every PR, developers start ignoring the results, and within two weeks the budget is deleted from the config. Start with warnings, build the habit, then tighten.
The GitHub Actions ubuntu-latest runner ships with Chrome, but you need to ensure it is in the PATH. LHCI typically finds it automatically. If you get a “Chrome could not be found” error, install it explicitly:
- name: Install Chrome
run: |
sudo apt-get update
sudo apt-get install -y google-chrome-stable
CI runners are noisy neighbors. CPU stealtime and memory pressure vary from run to run. Three mitigations help:
Run Lighthouse three times and take the median (numberOfRuns: 3). This filters out the outlier where your CI runner happened to be compiling a kernel module in the background.
Use settings.throttlingMethod: 'devtools' in your config instead of simulated throttling. DevTools throttling is more deterministic than the default simulated approach.
Budgets should have buffer. If your LCP is consistently 1.8 seconds on local, budget for 2.5 seconds in CI. The 700 ms cushion absorbs runner noise while still flagging a real 900 ms regression.
This is runner noise, not a real regression. Use numberOfRuns: 5 and set assertionLevel: 'warn' for metrics that are close to the boundary. Then add a secondary job that runs weekly against production with stricter budgets and no retries. The weekly job is the source of truth.
When the workflow finishes, the Lighthouse report artifact contains an HTML report for each URL tested. Download it from the Actions tab and open it in a browser. The report shows every audit, the metrics, the filmstrip, and the opportunities section with specific recommendations.
If you installed the LHCI GitHub app, every PR gets a status check with a summary of the budget results and a link to the full report. Example output looks like this:
✅ Performance: 94 (budget: 90)
✅ LCP: 2.1 s (budget: 2.5 s)
✅ TBT: 140 ms (budget: 200 ms)
✅ CLS: 0.05 (budget: 0.1)
✅ JS size: 187 KB (budget: 250 KB)
❌ Total page weight: 623 KB (budget: 600 KB)
The failing line tells you exactly which budget you blew and by how much. No ambiguity. The developer knows “my page weight is 23 KB over budget” and can look at their PR diff to find the culprit.
This blog runs on Astro. The setup is identical to what is shown above, with one adjustment: Astro’s default build output goes to dist/, which is what staticDistDir: './dist' expects. If you use the output: 'server' mode (SSR), switch to startServerCommand instead:
collect: {
startServerCommand: 'npm run preview',
url: ['http://localhost:4321'],
numberOfRuns: 3,
},
This starts the Astro preview server on port 4321, runs Lighthouse against it, and shuts the server down when the collection finishes.
Lighthouse audits more than speed. The assertions block also supports accessibility, SEO, and best-practice categories. You can set budgets for those too:
assertions: {
'categories:accessibility': ['error', { minScore: 0.95 }],
'categories:seo': ['error', { minScore: 0.95 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'aria-allowed-attr': ['error'],
'document-title': ['error'],
'meta-description': ['error'],
'viewport': ['error'],
}
Accessibility budgets are particularly valuable because they catch regressions that visual review misses. A new component that renders a <div> instead of a <button> passes code review but fails an accessibility audit. The PR gets blocked until the component uses the correct semantic element.
The per-PR workflow catches new regressions. But you also need a regular production audit that tracks trend data over time. Add a scheduled workflow that runs weekly against production with the same budget configuration:
name: Production Lighthouse Audit
on:
schedule:
- cron: '0 6 * * 1' # Every Monday at 6 AM
workflow_dispatch: # Manual trigger
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- name: Run Lighthouse against production
run: |
npx lhci autorun \
--config=./lighthouserc.production.js
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
The production config uses a different collect setup:
collect: {
url: ['https://your-production-site.com'],
numberOfRuns: 5,
settings: {
preset: 'desktop',
throttlingMethod: 'devtools',
},
},
Five runs instead of three, because production traffic adds variance that more samples smooth out. The results get uploaded and compared against the historical trend in the LHCI dashboard.
If you are setting this up right now and do not know what numbers to pick, use the Web Almanac’s 2024 median as your starting point. For mobile pages:
| Metric | Median p50 | Good (p75) | Great (p90) |
|---|---|---|---|
| LCP | 2.8 s | 2.5 s | 1.8 s |
| TBT | 300 ms | 200 ms | 100 ms |
| CLS | 0.15 | 0.1 | 0.05 |
| FCP | 2.2 s | 1.8 s | 1.2 s |
| JS (compressed) | 350 KB | 250 KB | 150 KB |
Start with “Good” as your error threshold. Tighten to “Great” over three months. This gives you room to ship features while the team adapts to the constraint.
Performance is a feature, and like any feature it needs tests. Lighthouse CI gives you those tests: a browser running against your actual build, measuring the same six metrics that determine your Core Web Vitals score, and asserting they stay above a hard threshold. The config file is 30 lines. The CI workflow is another 20. The setup takes an afternoon.
The alternative is what you have now: a production dashboard that blinks red at 3 a.m., a pager that goes off, and a frantic revert of a commit that shipped two weeks ago. Pick the approach that makes performance the CI’s job, not the SRE’s problem.
Setting up automated performance pipelines that catch regressions before they reach production requires more than just running a tool in CI. It demands careful budget calibration, understanding how your framework and hosting affect each Lighthouse metric, and ongoing maintenance as your application grows. Yojji’s engineering teams regularly build and maintain this kind of performance infrastructure for their clients, from CI/CD optimization to full-cycle product delivery across the JavaScript ecosystem and cloud platforms on AWS, Azure, and Google Cloud. Yojji is an international custom software development company founded in 2016, with offices in Europe, the US, and the UK, providing senior engineering talent that treats performance as a first-class requirement, not an afterthought.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。