Skip to content

Web Performance Budgets Guide

DodaTech Updated 2026-06-24 8 min read

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

  1. Budgets that are too loose — Setting budgets at 2x the current size gives no room for regressions. Start at current size minus 10%.
  2. Only tracking one metric — Focusing only on bundle size ignores images, fonts, and third-party scripts that often dominate page weight.
  3. Not running budgets in CI — Budgets enforced manually during development are forgotten. Automate them to block PRs that exceed limits.
  4. Ignoring mobile budgets — Desktop budgets are irrelevant for mobile users. Set separate budgets for mobile with tighter limits on CPU and network.
  5. 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

What is a performance budget?

A performance budget is a set of agreed-upon limits for page metrics (LCP, TBT, CLS) and resource sizes (JS, CSS, images, fonts). Building to these limits prevents performance regressions as features are added.

How do you enforce budgets in CI?

Use Lighthouse CI with a budget config file, Webpack's performance hints, or tools like bundlesize. Run them in a GitHub Action or equivalent CI pipeline that fails the build when limits are exceeded.

What is a good initial performance budget?

Start with: total page weight < 500KB, JS < 150KB, images < 250KB, LCP < 2.5s, TBT < 200ms, CLS < 0.1. Adjust based on your audience's network conditions.

How do image budgets affect Core Web Vitals?

Large images directly increase LCP (largest element often an image) and overall page weight. Setting an image budget forces teams to use modern formats (WebP, AVIF), responsive images, and lazy loading.

Can budgets apply to different page types?

Yes. Define separate budgets per route or template. A dashboard page can have a larger JS budget than a landing page. Lighthouse CI supports per-path budgets in the config file

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