Skip to content

Performance Budgeting for Static Sites — Core Web Vitals Guide

DodaTech Updated 2026-06-22 8 min read

In this tutorial, you'll learn about Performance Budgeting for Static Sites. We cover key concepts, practical examples, and best practices.

Performance budgeting sets hard limits on page weight, load time, and resource count — preventing regressions by failing builds that exceed thresholds and keeping static sites fast as they grow with new content and features.

What You'll Learn

Why It Matters

A single oversized image or unoptimized script can double your page load time, hurting Lighthouse scores, Core Web Vitals ratings, and search rankings. Without a performance budget, sites slowly degrade as teams add features, images, and scripts. Enforcing budgets in CI catches regressions before they reach production, protecting your user experience and SEO performance.

Real-World Use

A documentation site sets a 500KB JavaScript budget and a 1MB total page weight limit. When a developer adds a new syntax highlighting library, the CI build fails because the budget is exceeded. A marketing site enforces a 2.5-second Largest Contentful Paint (LCP) budget and blocks any pull request that drops below it.

Performance Budget Architecture

flowchart LR
  A[New Feature] --> B[Build]
  B --> C[Measure Metrics]
  C --> D{Budget Check}
  D -->|Pass| E[Deploy]
  D -->|Fail| F[Block Merge]
  F --> G[Developer Fixes]
  G --> A
  E --> H[Monitor Live]
  H --> I{Degradation?}
  I -->|Yes| J[Alert Team]
  I -->|No| K[Maintain Budget]
  style D fill:#f90,color:#fff
  style F fill:#f44,color:#fff
  style K fill:#090,color:#fff

What Is a Performance Budget?

A performance budget is a set of thresholds for key metrics that your site must not exceed. Breaking the budget means either optimizing the new addition or reconsidering its inclusion.

Common Budget Categories

Category Example Budget Why It Matters
Total page weight < 1MB Slower downloads on mobile networks
JavaScript budget < 300KB parsed Parsing time delays interactivity
Image budget < 500KB per page Images are the largest contributor to page weight
HTTP requests < 25 requests Connection overhead on HTTP/1.1
LCP < 2.5 seconds Largest Contentful Paint (Core Web Vital)
FID / TBT < 100ms / < 50ms First Input Delay (Core Web Vital)
CLS < 0.1 Cumulative Layout Shift (Core Web Vital)
Time to Interactive < 3.5 seconds When the page becomes usable

Core Web Vitals Targets

Google considers these metrics for search ranking:

# Performance budget thresholds (Google's recommended targets)
metrics:
  lcp:
    good: 2.5s  # Largest Contentful Paint
    poor: 4.0s
  fid:
    good: 100ms # First Input Delay
    poor: 300ms
  cls:
    good: 0.1   # Cumulative Layout Shift
    poor: 0.25
  inp:
    good: 200ms # Interaction to Next Paint (2024+)
    poor: 500ms

Enforcing Budgets with Lighthouse CI

Installation and Configuration

npm install -g @lhci/cli
// lighthouserc.json — Lighthouse CI configuration with budgets
{
  "ci": {
    "collect": {
      "numberOfRuns": 3,
      "staticDistDir": "./public",
      "settings": {
        "onlyCategories": ["performance", "accessibility", "seo"]
      }
    },
    "assert": {
      "preset": "lighthouse:recommended",
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "categories:accessibility": ["error", { "minScore": 0.9 }],
        "categories:seo": ["error", { "minScore": 0.9 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "total-blocking-time": ["error", { "maxNumericValue": 50 }],
        "max-server-response-time": ["error", { "maxNumericValue": 600 }],
        "uses-responsive-images": ["error", { "minScore": 1 }],
        "modern-image-formats": ["error", { "minScore": 1 }],
        "offscreen-images": ["error", { "minScore": 1 }],
        "unminified-css": ["error", { "minScore": 1 }],
        "unminified-javascript": ["error", { "minScore": 1 }]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

Expected behavior: Lighthouse CI builds the site, runs Lighthouse audits 3 times, and asserts that each metric meets the defined thresholds. If any assertion fails, Lighthouse CI exits with a non-zero code, causing the CI pipeline to fail.

GitHub Actions Integration

# .github/workflows/lighthouse.yml
name: Lighthouse CI

on:
  pull_request:
    branches: [main]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: '0.128.0'
          extended: true

      - name: Build site
        run: hugo --gc --minify

      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli
          lhci autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

Budget File with webpack

For sites using webpack-based SSGs (Next.js, Astro):

// performance-budget.js — webpack performance budgets
module.exports = {
  configureWebpack: {
    performance: {
      hints: 'error',
      maxEntrypointSize: 300000,  // 300KB
      maxAssetSize: 200000,       // 200KB
      assetFilter: function(assetFilename) {
        return assetFilename.endsWith('.js') || assetFilename.endsWith('.css');
      }
    }
  }
};

Tracking Budgets with bundlesize

// package.json — bundlesize budget configuration
{
  "bundlesize": [
    { "path": "./public/**/*.js", "maxSize": "200 kB" },
    { "path": "./public/**/*.css", "maxSize": "100 kB" },
    { "path": "./public/**/main.*.js", "maxSize": "50 kB" }
  ],
  "scripts": {
    "test:size": "bundlesize"
  }
}

Tools and Platforms Comparison

Tool Type What It Measures CI Integration Free Tier
Lighthouse CI Local audit LCP, CLS, TBT, score GitHub Actions, CircleCI Yes
WebPageTest Cloud test Full waterfall API, custom Limited
bundlesize File size Asset byte sizes npm script Yes
SiteSpeed.io Local + Cloud Full metrics, video Docker, plugins Yes
Cloudflare Observatory Edge CDN Core Web Vitals from RUM Dashboard Yes (with CF)
Pagespeed Insights API Google API Lab + field data REST API Yes

Optimization Strategies to Meet Budgets

Image Optimization

Images are the largest contributor to page weight. Use responsive images with modern formats like WebP:

{{/* Hugo — Responsive image with WebP */}}
{{ $image := resources.Get "images/hero.jpg" }}
{{ $webp := $image | resources.Fingerprint "md5" }}
{{ $image320 := $image.Resize "320x webp" }}
{{ $image640 := $image.Resize "640x webp" }}
{{ $image1280 := $image.Resize "1280x webp" }}

<picture>
  <source srcset="{{ $image320.RelPermalink }} 320w,
                  {{ $image640.RelPermalink }} 640w,
                  {{ $image1280.RelPermalink }} 1280w"
          sizes="(max-width: 320px) 320px, (max-width: 640px) 640px, 1280px"
          type="image/webp">
  <img src="{{ $image.RelPermalink }}"
       width="{{ $image.Width }}"
       height="{{ $image.Height }}"
       loading="lazy"
       alt="Hero image">
</picture>

CSS and JavaScript Optimization

# hugo.toml — Asset optimization
[build]
  writeStats = true

[params]
  [params.assets]
    cssMinify = true
    jsMinify = true

Font Optimization

/* Load only the characters you need */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/Inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF;  /* Latin only */
  font-display: swap;
}

Common Errors

1. Budget Too Strict (False Positives)

Setting budgets too aggressively (e.g., < 100KB total) causes constant build failures for legitimate additions. Start with Google's recommended thresholds, then tighten gradually based on real user monitoring data.

2. Budget Too Loose (No Benefit)

A 5MB budget on a static site provides no guardrails. The budget should be tight enough that a single unoptimized image or script addition would break it. Review budgets quarterly as site performance improves.

3. Not Differentiating by Page Type

A blog post and a dashboard page have different performance profiles. Set page-type-specific budgets using Lighthouse CI's --budget-file option, or segment by URL patterns.

4. Ignoring Third-Party Script Impact

Third-party scripts (analytics, widgets, fonts) often exceed the budget silently. Include them in your measurements and set specific budgets for third-party content. Use Resource Timing API to track them programmatically.

5. Not Monitoring Real User Data

Lab data (Lighthouse) and field data (Chrome User Experience Report) can differ. A page that passes Lighthouse budgets might still have poor real-world Core Web Vitals. Monitor both and set budgets for field data using the CrUX API.

Practice Questions

1. What are the three Core Web Vitals metrics and their recommended thresholds?

LCP (< 2.5s), FID (< 100ms), and CLS (< 0.1). Starting in 2024, INP replaces FID with a threshold of < 200ms.

2. How does Lighthouse CI enforce a performance budget?

Lighthouse CI runs Lighthouse audits and compares each metric against defined thresholds using assertions. If any assertion fails (e.g., LCP > 2.5s), Lighthouse CI exits with an error, which blocks the CI pipeline.

3. What is the difference between lab data and field data?

Lab data is collected in a controlled environment (Lighthouse, WebPageTest) with consistent network and device conditions. Field data comes from real users (Chrome UX Report) and reflects actual device capabilities and network conditions.

4. Why is image optimization the highest-leverage performance improvement?

Images account for 50-70% of total page weight on most sites. Converting to WebP/AVIF, serving responsive sizes, and lazy-loading offscreen images can reduce page weight by 60-80% with a single change.

5. Challenge: Set up performance budgets for a Hugo site using Lighthouse CI. Configure budgets for LCP (< 2.5s), CLS (< 0.1), TBT (< 50ms), total page weight (< 1MB), and JavaScript (< 300KB). Integrate into a GitHub Actions workflow that blocks pull requests exceeding any budget.

Mini Project: Performance Budget Dashboard

Create a performance budget system for a static site:

  1. Define budget thresholds in a lighthouserc.json file
  2. Integrate Lighthouse CI into your GitHub Actions pipeline
  3. Set up budges for both lab data (Lighthouse) and field data (CrUX API)
  4. Add a GitHub status check that shows pass/fail for each metric
  5. Create a performance dashboard page that displays current metrics against budgets using the CrUX API

Budget configuration template:

{
  "ci": {
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
        "total-blocking-time": ["error", { "maxNumericValue": 50 }],
        "total-byte-weight": ["error", { "maxNumericValue": 1000000 }],
        "uses-responsive-images": ["error", { "minScore": 1 }]
      }
    }
  }
}

Test the system by adding a 2MB unoptimized image to a page, pushing a branch, and verifying that the CI pipeline fails. Then optimize the image and confirm the pipeline passes.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro