Skip to main content

Performance Budgets

Performance budgets are limits for resource sizes and metrics that help maintain fast load times. Setting and enforcing budgets prevents performance regression over time.
Resource TypeBudgetRationale
Total page weight< 1.5 MB3G network loads in ~4s. Keeps LCP under 2.5s for median mobile users
JavaScript (compressed)< 300 KBParsing + execution time impact on TBT/INP. Each 100KB adds ~1s on low-end mobile
CSS (compressed)< 100 KBRender blocking resource. Larger CSS delays First Contentful Paint
Images (above-fold)< 500 KBDirect impact on LCP. Above-fold images must load quickly
Fonts< 100 KBFOIT/FOUT prevention. Excessive font files block text rendering
Third-party< 200 KBUncontrolled latency. Third-party scripts often load additional resources

Budget Breakdown by Page Type

Landing Pages

Focus on fast initial impression:
Total: 1 MB
- HTML: 50 KB
- CSS: 50 KB
- JavaScript: 200 KB
- Images: 400 KB
- Fonts: 100 KB
- Third-party: 200 KB

E-commerce Product Pages

Balance visuals with performance:
Total: 1.5 MB
- HTML: 50 KB
- CSS: 80 KB
- JavaScript: 300 KB
- Images: 700 KB (product photos)
- Fonts: 100 KB
- Third-party: 270 KB (reviews, analytics)

Content/Blog Pages

Prioritize text readability:
Total: 1 MB
- HTML: 100 KB
- CSS: 50 KB
- JavaScript: 150 KB
- Images: 400 KB
- Fonts: 100 KB
- Third-party: 200 KB

Web Applications

Allow for interactive features:
Total: 2 MB (initial load)
- HTML: 50 KB
- CSS: 100 KB
- JavaScript: 500 KB (with code splitting)
- Images: 300 KB
- Fonts: 100 KB
- Third-party: 150 KB

Metric Budgets

Core Web Vitals

MetricBudgetBased On
LCP< 2.5sGoogle’s “Good” threshold (75th percentile)
INP< 200msGoogle’s “Good” threshold
CLS< 0.1Google’s “Good” threshold
FCP< 1.8sFast First Contentful Paint
TBT< 200msLow Total Blocking Time

Network Metrics

MetricBudgetBased On
TTFB< 800msTime to First Byte on 4G
DOM Content Loaded< 1.5sDocument ready state
Load Event< 3sAll resources loaded

Size Metrics

MetricBudgetBased On
Number of Requests< 50HTTP/2 mitigates, but more = more processing
Main Thread Work< 2sKeeps TBT low
JavaScript Execution Time< 1sAllows for fast interactions

Measuring Against Budgets

Using Lighthouse CLI

# Run Lighthouse with budgets
lighthouse https://example.com \
  --budget-path=budget.json \
  --output=html \
  --output-path=report.html
budget.json:
[
  {
    "path": "/*",
    "resourceSizes": [
      {
        "resourceType": "script",
        "budget": 300
      },
      {
        "resourceType": "stylesheet",
        "budget": 100
      },
      {
        "resourceType": "image",
        "budget": 500
      },
      {
        "resourceType": "font",
        "budget": 100
      },
      {
        "resourceType": "third-party",
        "budget": 200
      },
      {
        "resourceType": "total",
        "budget": 1500
      }
    ],
    "resourceCounts": [
      {
        "resourceType": "third-party",
        "budget": 10
      },
      {
        "resourceType": "total",
        "budget": 50
      }
    ]
  }
]

Using Webpack Bundle Analyzer

# Install
npm install --save-dev webpack-bundle-analyzer

# Add to webpack config
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  }
};

# Build and analyze
npm run build

Using bundlesize

# Install
npm install --save-dev bundlesize

# Add to package.json
{
  "bundlesize": [
    {
      "path": "./dist/js/*.js",
      "maxSize": "300 KB"
    },
    {
      "path": "./dist/css/*.css",
      "maxSize": "100 KB"
    }
  ],
  "scripts": {
    "test:size": "bundlesize"
  }
}

# Run check
npm run test:size

Using Chrome DevTools

  1. Network Panel:
    • View total transferred size
    • Filter by resource type
    • Check individual file sizes
  2. Coverage Panel:
    • Identify unused CSS/JS
    • See code utilization percentage
    • Find opportunities to reduce bundle
  3. Performance Panel:
    • Measure Core Web Vitals
    • Analyze main thread work
    • Identify long tasks

Enforcing Budgets

CI/CD Integration

GitHub Actions Example:
name: Performance Budget

on: [pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v9
        with:
          urls: |
            https://example.com
          budgetPath: ./budget.json
          uploadArtifacts: true
lighthouserc.js:
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'resource-summary:script:size': ['error', { maxNumericValue: 307200 }],
        'resource-summary:stylesheet:size': ['error', { maxNumericValue: 102400 }],
        'resource-summary:image:size': ['error', { maxNumericValue: 512000 }],
        'resource-summary:font:size': ['error', { maxNumericValue: 102400 }],
        'total-byte-weight': ['error', { maxNumericValue: 1536000 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Webpack Performance Hints

// webpack.config.js
module.exports = {
  performance: {
    maxAssetSize: 300000, // 300 KB
    maxEntrypointSize: 500000, // 500 KB
    hints: 'error', // or 'warning'
  },
};

Next.js Bundle Analysis

// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
  // Your Next.js config
});

// Run with:
// ANALYZE=true npm run build

Optimizing to Meet Budgets

JavaScript Budget Exceeded

Solutions:
  • Implement code splitting
  • Remove unused dependencies
  • Use tree-shaking
  • Lazy load non-critical features
  • Replace heavy libraries with lighter alternatives
// Before: Import entire library (50 KB)
import _ from 'lodash';

// After: Import only needed function (5 KB)
import debounce from 'lodash/debounce';

CSS Budget Exceeded

Solutions:
  • Remove unused CSS
  • Use critical CSS inlining
  • Split CSS by route
  • Use CSS-in-JS for component-scoped styles
  • Minimize specificity and duplication
# Remove unused CSS
npx purgecss --css style.css --content index.html --output dist/

Image Budget Exceeded

Solutions:
  • Compress images (use WebP/AVIF)
  • Implement lazy loading
  • Use responsive images with srcset
  • Serve correctly sized images
  • Use CDN with automatic optimization
<!-- Responsive images -->
<img 
  src="image-800.webp"
  srcset="image-400.webp 400w, image-800.webp 800w, image-1200.webp 1200w"
  sizes="(max-width: 600px) 100vw, 50vw"
  loading="lazy"
  alt="Description">

Font Budget Exceeded

Solutions:
  • Use variable fonts (one file for all weights)
  • Subset fonts to required characters
  • Preload critical fonts only
  • Use font-display: swap or optional
  • Consider system font stack
/* System font stack (0 KB) */
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* Or subset custom font */
@font-face {
  font-family: 'Custom';
  src: url('custom-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF; /* Latin only */
  font-display: swap;
}

Third-Party Budget Exceeded

Solutions:
  • Defer non-critical third-party scripts
  • Self-host when possible
  • Use facade pattern for embeds
  • Lazy load analytics
  • Review necessity of each third-party
// Lazy load analytics
if ('requestIdleCallback' in window) {
  requestIdleCallback(() => {
    loadAnalytics();
  });
} else {
  setTimeout(loadAnalytics, 2000);
}

Monitoring Budgets Over Time

Tools

  • Lighthouse CI: Continuous monitoring in CI/CD
  • SpeedCurve: Performance monitoring with budget alerts
  • Calibre: Performance budgets with regression detection
  • WebPageTest: One-off testing with detailed metrics
  • Google Search Console: Core Web Vitals field data

Setting Up Alerts

Create alerts when budgets are exceeded:
// Example: Slack webhook on budget failure
if (bundleSize > BUDGET) {
  await fetch(SLACK_WEBHOOK, {
    method: 'POST',
    body: JSON.stringify({
      text: `⚠️ Bundle size exceeded: ${bundleSize}KB > ${BUDGET}KB`
    })
  });
}

Budget Examples by Industry

News/Media

  • Total: 1.5 MB (image-heavy)
  • Focus on LCP for hero image
  • Lazy load article images

SaaS Dashboard

  • Total: 2.5 MB (initial), 500 KB (route change)
  • Code splitting by route essential
  • Focus on INP for interactions

E-commerce

  • Total: 1.5 MB
  • Balance product images with speed
  • Optimize for mobile shoppers

Corporate/Marketing

  • Total: 1 MB
  • Fast LCP critical for conversions
  • Minimal JavaScript

Additional Resources

Build docs developers (and LLMs) love