Web Performance Budgets: Setting and Enforcing Performance Targets | SoniNow Blog

Limited TimeLearn More

performance budgetsweb perflighthousemonitoringci/cd

Web Performance Budgets: Setting and Enforcing Performance Targets

Published

2026-06-23

Read Time

3 mins

Web Performance Budgets: Setting and Enforcing Performance Targets

A performance budget is your contract with the user—a measurable threshold that every page must meet. Without one, performance degrades silently, one extra npm dependency at a time. Setting and enforcing budgets turns performance from a vague goal into a hard requirement.

What Belongs in a Performance Budget

Budgets fall into three categories. Quantity budgets cap absolute metrics: total JavaScript (300 KB gzipped), total images (500 KB per page), or total requests (25 per page). Timing budgets target user-centric metrics: LCP under 2.5 seconds, INP under 200 ms, CLS under 0.1. Rule budgets enforce pattern-based constraints: no render-blocking third-party scripts, all images must use modern formats.

Start with timing budgets based on CrUX data for your industry. An e-commerce checkout page should have tighter budgets than a documentation page.

Bundle Size Budgets with webpack and esbuild

Modern bundlers support size budgets natively. With webpack, use performance.maxAssetSize:

// webpack.config.js
module.exports = {
  performance: {
    maxAssetSize: 250000,       // 250 KB max per asset
    maxEntrypointSize: 350000,  // 350 KB max for entry point
    hints: 'error',             // Fail the build on violation
  },
};

For esbuild or Vite, use the --metafile flag and a post-build check:

// scripts/check-budget.js
import { readFileSync } from 'fs';

const meta = JSON.parse(readFileSync('meta.json'));
const total = Object.values(meta.outputs)
  .reduce((sum, o) => sum + o.bytes, 0);

if (total > 300_000) {
  process.exit(1); // Fail CI
}

Lighthouse Budgets in CI

Lighthouse CI supports budget files that check multiple metrics in one pass:

{
  "ci": {
    "assert": {
      "budgetsFile": "./lighthouse-budgets.json"
    }
  }
}

The budgets file itself:

[
  {
    "resourceCounts": [
      { "resourceType": "total", "budget": 30 },
      { "resourceType": "script", "budget": 8 },
      { "resourceType": "image", "budget": 12 }
    ],
    "resourceSizes": [
      { "resourceType": "total", "budget": 700 },
      { "resourceType": "script", "budget": 350 },
      { "resourceType": "image", "budget": 500 }
    ],
    "timings": [
      { "metric": "largest-contentful-paint", "budget": 2500 },
      { "metric": "cumulative-layout-shift", "budget": 0.1 }
    ]
  }
]

Run this on every pull request. When a branch exceeds the budget, the CI check fails and the developer sees exactly which metric is over.

Third-Party Script Caps

Third-party scripts are the most unpredictable budget-busters. Set explicit limits using a performance observer in your development environment:

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.initiatorType === 'script' && entry.duration > 500) {
      console.warn(`Slow third-party script: ${entry.name} (${entry.duration}ms)`);
    }
  }
}).observe({ entryTypes: ['resource'] });

For production, use subresource integrity (SRI) and a Content Security Policy to allow only approved third-party origins. If a script exceeds your budget's third-party weight, swap to a lighter alternative or load it post-interaction.

Keeping Budgets Current

Budgets shouldn't be static. Review them quarterly against real-user data. If your median INP drops from 180 ms to 120 ms, tighten the budget. If a new feature genuinely requires more JavaScript, adjust the budget knowingly—then optimize to bring it back down.

A good budget is a negotiation, not a fence. It forces you to account for every kilobyte and every millisecond, making performance a first-class concern throughout development.

Need help setting up performance budgets that stick? Our web development team can audit your current bundle sizes, define custom budgets, and integrate enforcement into your CI pipeline.