A performance budget is a set of limits on metrics that affect user experience. Without budgets, performance degrades with every feature addition.
What to Budget
Quantity-Based Budgets
Total JavaScript: < 200KB gzipped
Total CSS: < 50KB gzipped
Total images: < 500KB
Total page weight: < 1MB
Number of requests: < 50Timing-Based Budgets
Time to Interactive (TTI): < 3.5s on 4G
Largest Contentful Paint (LCP): < 2.5s
First Contentful Paint (FCP): < 1.8s
Interaction to Next Paint (INP): < 200ms
Cumulative Layout Shift (CLS): < 0.1Enforcing in CI
// bundlesize config (package.json)
{
"bundlesize": [
{ "path": "dist/js/*.js", "maxSize": "200 kB" },
{ "path": "dist/css/*.css", "maxSize": "50 kB" }
]
}
// Lighthouse CI
// .lighthouserc.js
module.exports = {
ci: {
assert: {
assertions: {
"first-contentful-paint": ["error", { maxNumericValue: 1800 }],
"interactive": ["error", { maxNumericValue: 3500 }],
"largest-contentful-paint": ["error", { maxNumericValue: 2500 }],
"cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
"total-byte-weight": ["warning", { maxNumericValue: 1000000 }]
}
}
}
};Webpack Bundle Analysis
// Install
npm install --save-dev webpack-bundle-analyzer
// Add to webpack config
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
plugins: [new BundleAnalyzerPlugin()]
// For Vite
npx vite-bundle-visualizerCommon Budget Violations
| Violation | Common Cause | Fix |
|---|---|---|
| JS too large | Importing entire lodash | import { debounce } from "lodash-es" |
| CSS too large | Unused styles | PurgeCSS / Tailwind purge |
| Images too large | Unoptimized images | WebP/AVIF, responsive images |
| Too many requests | No code splitting strategy | Dynamic imports, lazy loading |
Monitoring in Production
- Real User Monitoring (RUM) with web-vitals library
- Synthetic monitoring with Lighthouse CI
- Set up alerts when metrics exceed thresholds
- Track metrics over time in dashboards