Web Performance Budgets Guide
In this tutorial, you'll learn about Web Performance Budgets Guide. We cover key concepts, practical examples, and best practices.
A performance budget is a set of limits — max bundle size, max image weight, max LCP — that your team agrees not to exceed during development.
In this tutorial, you'll learn how performance budgets work — defining limits for JavaScript bundles, images, and Core Web Vitals, measuring against them with automated tools, and enforcing them in CI/CD pipelines. Performance budgets prevent regressions before they reach production, keeping your site fast as it grows. By the end, you'll configure Lighthouse CI to enforce budgets on every pull request.
Real-world use: Doda Browser's new tab page enforces a 50KB JavaScript budget and an LCP under 1.5 seconds. Durga Antivirus Pro's web dashboard has a 200KB total page weight budget for the scan results view.
flowchart TD A[Define Budget] --> B[Budget Types] B --> C[Metric Budgets] B --> D[Resource Budgets] B --> E[Third-Party Budgets] C --> F[LCP < 2.5s] C --> G[TBT < 200ms] C --> H[CLS < 0.1] D --> I[JS < 150KB] D --> J[Images < 500KB] D --> K[Fonts < 100KB] E --> L[Analytics < 30KB] E --> M[Ads < 200KB] F & G & H & I & J & K & L & M --> N[CI Enforcement] N --> O[Pass / Fail] O --> P[Deploy / Block]
Defining a Performance Budget
Start by measuring your current site, then set limits slightly tighter.
{
"budgets": [
{
"resourceSizes": [
{ "resourceType": "total", "budget": 500 },
{ "resourceType": "script", "budget": 150 },
{ "resourceType": "stylesheet", "budget": 30 },
{ "resourceType": "image", "budget": 250 },
{ "resourceType": "font", "budget": 50 },
{ "resourceType": "document", "budget": 25 },
{ "resourceType": "third-party", "budget": 100 }
],
"resourceCounts": [
{ "resourceType": "script", "budget": 10 },
{ "resourceType": "image", "budget": 20 },
{ "resourceType": "third-party", "budget": 5 }
],
"timings": [
{ "metric": "first-contentful-paint", "budget": 2000 },
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "total-blocking-time", "budget": 200 },
{ "metric": "cumulative-layout-shift", "budget": 0.1 },
{ "metric": "speed-index", "budget": 3000 }
]
}
]
}
All sizes are in kilobytes. All timings are in milliseconds except CLS (score) and Speed Index (milliseconds).
Measuring with Lighthouse
Run Lighthouse programmatically to check budget compliance.
npx lighthouse https://example.com --chrome-flags="--headless" --output=json --output-path=./report.json
Parse the report against your budget:
import fs from 'fs';
const report = JSON.parse(fs.readFileSync('./report.json', 'utf-8'));
const audits = report.audits;
const metrics = {
'first-contentful-paint': audits['first-contentful-paint'].numericValue,
'largest-contentful-paint': audits['largest-contentful-paint'].numericValue,
'total-blocking-time': audits['total-blocking-time'].numericValue,
'cumulative-layout-shift': audits['cumulative-layout-shift'].numericValue,
'speed-index': audits['speed-index'].numericValue
};
const budget = {
'first-contentful-paint': 2000,
'largest-contentful-paint': 2500,
'total-blocking-time': 200,
'cumulative-layout-shift': 0.1,
'speed-index': 3000
};
let allPassed = true;
for (const [metric, value] of Object.entries(metrics)) {
const limit = budget[metric];
const passes = value <= limit;
console.log(`${metric}: ${value.toFixed(1)} / ${limit} ${passes ? 'PASS' : 'FAIL'}`);
if (!passes) allPassed = false;
}
console.log(`\nOverall: ${allPassed ? 'PASS' : 'FAIL'}`);
process.exit(allPassed ? 0 : 1);
Expected output:
first-contentful-paint: 1450.0 / 2000 PASS
largest-contentful-paint: 2100.0 / 2500 PASS
total-blocking-time: 85.0 / 200 PASS
cumulative-layout-shift: 0.02 / 0.1 PASS
speed-index: 2200.0 / 3000 PASS
Overall: PASS
Webpack Bundle Budgets
Enforce JavaScript bundle size limits directly in your build tooling.
// webpack.config.js
const entry = {
main: './src/index.js',
dashboard: './src/dashboard.js'
};
const performance = {
hints: 'error',
maxEntrypointSize: 250000, // 250KB
maxAssetSize: 100000 // 100KB
};
module.exports = {
entry,
performance,
// ... other config
};
When a bundle exceeds the budget, Webpack fails the build:
Expected output:
ERROR in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (250 KB).
This can impact web performance.
main (312 KB)
Lighthouse Budget File
Lighthouse CI and Lighthouse CLI support a standard budget format via a JSON file.
{
"budgets": [
{
"path": "/",
"options": {
"first-contentful-paint": { "maxNumericValue": 2000 },
"largest-contentful-paint": { "maxNumericValue": 2500 },
"cumulative-layout-shift": { "maxNumericValue": 0.1 },
"total-blocking-time": { "maxNumericValue": 200 },
"interactive": { "maxNumericValue": 3500 }
}
},
{
"path": "/dashboard",
"options": {
"largest-contentful-paint": { "maxNumericValue": 3000 },
"total-blocking-time": { "maxNumericValue": 300 }
}
}
]
}
Run with Lighthouse:
npx lighthouse https://example.com --budget-file=lighthouse-budget.json --output=json
Using bundlesize for Package-Level Budgets
The bundlesize tool checks individual file sizes against declared limits.
// package.json
{
"scripts": {
"check-size": "bundlesize"
},
"bundlesize": [
{ "path": "./dist/main.js", "maxSize": "150KB" },
{ "path": "./dist/vendor.js", "maxSize": "80KB" },
{ "path": "./dist/styles.css", "maxSize": "20KB" },
{ "path": "./assets/images/**/*.{png,jpg,webp}", "maxSize": "300KB" }
]
}
npm run build
npm run check-size
Expected output:
PASS ./dist/main.js: 142.5KB < 150KB
PASS ./dist/vendor.js: 65.2KB < 80KB
PASS ./dist/styles.css: 18.1KB < 20KB
FAIL ./dist/assets/images/*.webp: 450KB > 300KB
Image Budget Enforcement
Images are typically the largest resource on a page. Use a script to check total image weight.
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
const IMAGE_BUDGET = 500 * 1024; // 500KB
const imageDir = path.resolve('./static/images');
const imageFiles = glob.sync('**/*.{png,jpg,jpeg,webp,avif}', {
cwd: imageDir
});
let totalBytes = 0;
for (const file of imageFiles) {
const stats = fs.statSync(path.join(imageDir, file));
totalBytes += stats.size;
}
const totalKB = (totalBytes / 1024).toFixed(1);
const budgetKB = (IMAGE_BUDGET / 1024).toFixed(1);
const passes = totalBytes <= IMAGE_BUDGET;
console.log(`Total image size: ${totalKB}KB / ${budgetKB}KB ${passes ? 'PASS' : 'FAIL'}`);
if (!passes) {
console.log('Optimize images: convert to WebP/AVIF, reduce quality, or lazy-load.');
process.exit(1);
}
Third-Party Budget Tracking
Third-party scripts are a common source of budget bloat. Use Lighthouse's third-party summary or a performance observer.
// Monitor third-party impact at runtime
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const thirdParty = entries.filter(e => {
try {
const url = new URL(e.name);
return url.hostname !== window.location.hostname;
} catch { return false; }
});
const totalTransfer = thirdParty.reduce((sum, e) => sum + e.transferSize, 0);
console.log(`Third-party transfer size: ${(totalTransfer / 1024).toFixed(1)}KB`);
if (totalTransfer > 100 * 1024) {
console.warn('Third-party budget exceeded: >100KB transferred');
}
});
observer.observe({ type: 'resource', buffered: true });
CI/CD Budget Enforcement
The most effective setup enforces budgets automatically on every pull request.
# .github/workflows/performance-budget.yml
name: Performance Budget
on: [pull_request]
jobs:
budget:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run build
- name: Check bundle sizes
run: npm run check-size
- name: Lighthouse Budget
uses: treosh/lighthouse-ci-action@v10
with:
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true
- name: Enforce image budget
run: node scripts/check-image-budget.js
Common Errors
- Budgets that are too loose — Setting budgets at 2x the current size gives no room for regressions. Start at current size minus 10%.
- Only tracking one metric — Focusing only on bundle size ignores images, fonts, and third-party scripts that often dominate page weight.
- Not running budgets in CI — Budgets enforced manually during development are forgotten. Automate them to block PRs that exceed limits.
- Ignoring mobile budgets — Desktop budgets are irrelevant for mobile users. Set separate budgets for mobile with tighter limits on CPU and network.
- No third-party budget — A single analytics script can add 50KB+. Without a third-party budget, marketing teams can silently double your page weight.
Practice Questions
Challenge
Implement a comprehensive budget system for a Jamstack site. Create budgets for total page weight (300KB), JS (100KB), images (150KB), LCP (2s), and third-party (50KB). Write a Lighthouse budget config, a Webpack performance config, and a CI workflow. Test by adding a 200KB image and a 50KB third-party script, verifying the pipeline fails.
Real-World Task
The Doda Browser landing page has grown to 800KB total weight over six months. Recover performance by: defining budgets at 400KB total, 120KB JS, 200KB images, and LCP under 2s; converting hero images to AVIF; code-splitting the hero animation bundle; and configuring Lighthouse CI to block any PR that exceeds budgets. Document the performance improvement in a weekly report.
Related: Web Performance Optimization | Next: Lighthouse CI Guide | Previous: Core Web Vitals
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro