Web Vitals Budget Management | Blue Frog Docs

Web Vitals Budget Management

Setting and managing web vitals budgets to maintain optimal user experience and search performance

Web Vitals Budget Management

What This Means

A Web Vitals budget is a set of performance thresholds you establish for Core Web Vitals (LCP, INP, CLS) and other metrics to ensure your site maintains optimal user experience. Think of it like a financial budget - you allocate resources (loading time, script execution, layout shifts) across different page elements while staying within acceptable limits.

Core Web Vitals Thresholds

Good Performance (Green):

Needs Improvement (Yellow):

  • LCP: 2.5 - 4.0 seconds
  • INP: 200 - 500 milliseconds
  • CLS: 0.1 - 0.25

Poor Performance (Red):

  • LCP: > 4.0 seconds
  • INP: > 500 milliseconds
  • CLS: > 0.25

Why Budgets Matter

Performance Governance:

  • Prevents performance degradation over time
  • Establishes accountability for page speed
  • Enables data-driven decisions about features
  • Creates performance culture in development

Business Impact:

  • 100ms faster LCP = 1% increase in conversion (varies by industry)
  • Sites meeting Core Web Vitals see 24% less abandonment
  • Good INP scores correlate with higher engagement
  • CLS issues directly reduce trust and conversions

SEO Benefits:

  • Core Web Vitals are Google ranking factors
  • Better metrics = better search visibility
  • Improved mobile search rankings
  • Enhanced user signals (lower bounce rates)

How to Diagnose

Method 1: Chrome User Experience Report (CrUX)

Check Real User Metrics:

  1. Visit PageSpeed Insights
  2. Enter your URL
  3. Review "Discover what your real users are experiencing" section
  4. Check metrics at 75th percentile (Google's threshold)

What to Look For:

  • Green checkmarks for all Core Web Vitals
  • Field data from actual users (not lab data)
  • Mobile vs desktop differences
  • Origin-level vs page-level metrics
  • Percentage of "good" experiences

Method 2: Google Search Console

  1. Navigate to Google Search Console
  2. Click "Core Web Vitals" in left sidebar
  3. Review "Poor," "Need Improvement," and "Good" URLs
  4. Identify which pages fail thresholds

What to Look For:

  • Number of URLs in each category
  • Specific pages with issues
  • Mobile vs desktop performance gaps
  • Trends over time
  • Impact on total impressions

Method 3: Lighthouse Performance Budget

  1. Open Chrome DevTools (F12)
  2. Navigate to "Lighthouse" tab
  3. Click "⚙️" (settings gear)
  4. Enable "Performance budget"
  5. Configure budgets for:
    • Resource counts (scripts, stylesheets, images)
    • Resource sizes (total JS, total CSS, total images)
    • Timings (FCP, LCP, TBT)

Example Budget Configuration:

{
  "resourceSizes": [
    {
      "resourceType": "script",
      "budget": 300
    },
    {
      "resourceType": "stylesheet",
      "budget": 50
    },
    {
      "resourceType": "image",
      "budget": 500
    },
    {
      "resourceType": "total",
      "budget": 1000
    }
  ],
  "timings": [
    {
      "metric": "interactive",
      "budget": 3000
    },
    {
      "metric": "first-contentful-paint",
      "budget": 1800
    }
  ]
}

Method 4: Web Vitals JavaScript Library

Measure Real User Performance:

<script type="module">
  import {onCLS, onINP, onLCP} from 'https://unpkg.com/web-vitals@3?module';

  const vitalsUrl = 'https://your-analytics-endpoint.com/vitals';

  function sendToAnalytics(metric) {
    const body = JSON.stringify(metric);

    // Check against budget thresholds
    const budgets = {
      LCP: 2500,
      INP: 200,
      CLS: 0.1
    };

    const status = metric.value <= budgets[metric.name] ? 'good' : 'poor';

    console.log(`${metric.name}: ${metric.value} (${status})`);

    // Send to your analytics
    if (navigator.sendBeacon) {
      navigator.sendBeacon(vitalsUrl, body);
    }
  }

  onCLS(sendToAnalytics);
  onINP(sendToAnalytics);
  onLCP(sendToAnalytics);
</script>

Method 5: WebPageTest Performance Budgets

  1. Visit WebPageTest.org
  2. Enter your URL
  3. Click "Advanced Settings"
  4. Scroll to "Performance Budget"
  5. Set thresholds for:
    • Speed Index
    • LCP
    • TBT (Total Blocking Time)
    • CLS
    • Resource sizes

What to Look For:

  • Tests that fail budget requirements
  • Waterfall chart showing bottlenecks
  • Filmstrip view of loading experience
  • Specific resources exceeding budget

General Fixes

Fix 1: Establish Component-Level Budgets

Break down page budget by component:

  1. Header/Navigation:

    • LCP contribution: < 500ms
    • JavaScript: < 50KB
    • Images: < 100KB
    • CLS contribution: 0
  2. Hero/Above-Fold Content:

    • LCP contribution: < 1500ms (this is your LCP element)
    • JavaScript: < 100KB
    • Images: < 300KB (use modern formats)
    • CLS contribution: 0
  3. Main Content:

    • INP budget: < 100ms per interaction
    • JavaScript: < 150KB
    • Total size: < 500KB
    • CLS contribution: < 0.05
  4. Third-Party Scripts:

    • Total budget: < 200KB
    • Execution time: < 300ms
    • No render-blocking
    • Load after critical content

Example Budget Spreadsheet:

Component JS Budget CSS Budget Image Budget LCP Impact CLS Budget
Header 50KB 20KB 100KB 500ms 0
Hero 100KB 30KB 300KB 1500ms 0
Content 150KB 40KB 500KB - 0.05
Footer 30KB 20KB 100KB - 0.05
3rd Party 200KB - - 300ms 0
Total 530KB 110KB 1000KB 2300ms 0.1

Fix 2: Implement Automated Budget Monitoring

CI/CD Performance Checks:

  1. Lighthouse CI Configuration:

    Create lighthouserc.json:

    {
      "ci": {
        "collect": {
          "numberOfRuns": 3,
          "url": [
            "http://localhost:3000/",
            "http://localhost:3000/products",
            "http://localhost:3000/checkout"
          ]
        },
        "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": 200}],
            "interactive": ["error", {"maxNumericValue": 3500}],
            "first-contentful-paint": ["error", {"maxNumericValue": 1800}],
            "resource-summary:script:size": ["error", {"maxNumericValue": 300000}],
            "resource-summary:stylesheet:size": ["error", {"maxNumericValue": 50000}],
            "resource-summary:image:size": ["error", {"maxNumericValue": 500000}]
          }
        },
        "upload": {
          "target": "temporary-public-storage"
        }
      }
    }
    
  2. GitHub Actions Workflow:

    name: Performance Budget CI
    
    on: [pull_request]
    
    jobs:
      lighthouse:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - uses: actions/setup-node@v3
          - name: Install dependencies
            run: npm ci
          - name: Build
            run: npm run build
          - name: Run Lighthouse CI
            run: |
              npm install -g @lhci/cli
              lhci autorun
          - name: Upload results
            uses: actions/upload-artifact@v3
            with:
              name: lighthouse-results
              path: .lighthouseci
    
  3. Bundle Size Monitoring:

    // bundlesize.config.js
    module.exports = {
      files: [
        {
          path: './dist/main.js',
          maxSize: '300 KB'
        },
        {
          path: './dist/main.css',
          maxSize: '50 KB'
        },
        {
          path: './dist/vendor.js',
          maxSize: '200 KB'
        }
      ]
    };
    

Fix 3: Optimize for LCP Budget

Ensure LCP stays under 2.5 seconds:

  1. Identify your LCP element:

    new PerformanceObserver((list) => {
      const entries = list.getEntries();
      const lastEntry = entries[entries.length - 1];
      console.log('LCP element:', lastEntry.element);
      console.log('LCP time:', lastEntry.startTime);
    }).observe({type: 'largest-contentful-paint', buffered: true});
    
  2. Optimize LCP element loading:

    <!-- If LCP is an image, preload it -->
    <link rel="preload" as="image" href="/hero-image.webp" fetchpriority="high">
    
    <!-- Or use fetchpriority attribute -->
    <img src="/hero-image.webp" fetchpriority="high" alt="Hero">
    
  3. Reduce LCP element render time:

    • Remove render-blocking CSS for above-fold content
    • Inline critical CSS
    • Defer non-critical JavaScript
    • Use fast web fonts or system fonts
    • Optimize images (WebP, AVIF, proper sizing)
  4. Improve server response time (TTFB):

    • Use CDN for static assets
    • Implement server-side caching
    • Optimize database queries
    • Use HTTP/2 or HTTP/3
    • Enable compression (Brotli, gzip)

Fix 4: Manage INP Budget

Keep interactions under 200ms:

  1. Identify slow interactions:

    new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.duration > 200) {
          console.warn('Slow interaction:', {
            name: entry.name,
            duration: entry.duration,
            target: entry.target
          });
        }
      }
    }).observe({type: 'event', buffered: true, durationThreshold: 200});
    
  2. Break up long tasks:

    // Bad: Long synchronous task
    function processLargeArray(items) {
      for (let i = 0; i < items.length; i++) {
        processItem(items[i]);
      }
    }
    
    // Good: Yield to main thread
    async function processLargeArray(items) {
      for (let i = 0; i < items.length; i++) {
        processItem(items[i]);
    
        // Yield every 50 items
        if (i % 50 === 0) {
          await new Promise(resolve => setTimeout(resolve, 0));
        }
      }
    }
    
  3. Debounce expensive operations:

    function debounce(func, wait) {
      let timeout;
      return function executedFunction(...args) {
        const later = () => {
          clearTimeout(timeout);
          func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
      };
    }
    
    // Use for search, resize, scroll
    const handleSearch = debounce((query) => {
      performExpensiveSearch(query);
    }, 300);
    
  4. Use web workers for heavy computation:

    // worker.js
    self.addEventListener('message', (e) => {
      const result = performHeavyCalculation(e.data);
      self.postMessage(result);
    });
    
    // main.js
    const worker = new Worker('worker.js');
    worker.postMessage(data);
    worker.addEventListener('message', (e) => {
      console.log('Result:', e.data);
    });
    

Fix 5: Control CLS Budget

Maintain CLS under 0.1:

  1. Reserve space for dynamic content:

    /* Reserve space for images */
    img {
      aspect-ratio: 16 / 9;
      width: 100%;
      height: auto;
    }
    
    /* Or use explicit dimensions */
    <img src="image.jpg" width="800" height="600" alt="...">
    
  2. Avoid inserting content above existing content:

    // Bad: Inserting banner above content
    document.body.insertBefore(banner, document.body.firstChild);
    
    // Good: Reserve space for banner in HTML
    <div id="banner-placeholder" style="min-height: 100px;">
      <!-- Banner loaded here -->
    </div>
    
  3. Use transform for animations:

    /* Bad: Causes layout shift */
    .element {
      transition: margin-top 0.3s;
    }
    .element:hover {
      margin-top: -10px;
    }
    
    /* Good: No layout shift */
    .element {
      transition: transform 0.3s;
    }
    .element:hover {
      transform: translateY(-10px);
    }
    
  4. Load web fonts properly:

    @font-face {
      font-family: 'CustomFont';
      src: url('/fonts/custom.woff2') format('woff2');
      font-display: swap; /* or optional */
    }
    
    /* Fallback font with similar metrics */
    body {
      font-family: 'CustomFont', 'Arial', sans-serif;
    }
    

Fix 6: Monitor and Alert on Budget Violations

Set up real-time monitoring:

  1. Real User Monitoring (RUM):

    import {onCLS, onINP, onLCP} from 'web-vitals';
    
    const budgets = {
      LCP: 2500,
      INP: 200,
      CLS: 0.1
    };
    
    function sendVitals(metric) {
      // Send to analytics
      gtag('event', metric.name, {
        value: Math.round(metric.value),
        metric_id: metric.id,
        metric_delta: metric.delta,
        budget_status: metric.value <= budgets[metric.name] ? 'pass' : 'fail'
      });
    
      // Alert if budget violated
      if (metric.value > budgets[metric.name]) {
        console.warn(`Budget violated: ${metric.name} = ${metric.value}`);
        // Send alert to monitoring system
      }
    }
    
    onCLS(sendVitals);
    onINP(sendVitals);
    onLCP(sendVitals);
    
  2. Set up alerts in Google Analytics:

    • Create custom alerts for Core Web Vitals
    • Set thresholds based on your budgets
    • Receive email notifications when violated
  3. Use Cloudflare Web Analytics or similar:

    • Monitor Core Web Vitals across all pages
    • Set up budget thresholds
    • Get alerts when performance degrades

Fix 7: Create Performance Budget Culture

Make budgets part of development workflow:

  1. Document budgets clearly:

    • Include in README
    • Add to style guide
    • Make visible in project management tools
  2. Review performance in PRs:

    • Require Lighthouse CI to pass
    • Review bundle size changes
    • Discuss performance impact of new features
  3. Regular performance audits:

    • Weekly/monthly review of Core Web Vitals
    • Identify pages violating budgets
    • Prioritize fixes
  4. Performance champions:

    • Assign team members to monitor budgets
    • Share performance wins
    • Create accountability

Platform-Specific Guides

Detailed implementation instructions for your specific platform:

Platform Troubleshooting Guide
Shopify Shopify Performance Budget Guide
WordPress WordPress Performance Budget Guide
Wix Wix Performance Budget Guide
Squarespace Squarespace Performance Budget Guide
Webflow Webflow Performance Budget Guide

Verification

After implementing performance budgets:

  1. Run Lighthouse with budgets enabled:

    • Should pass all budget assertions
    • No warnings about exceeded budgets
    • Performance score > 90
  2. Check CrUX data (wait 28 days):

    • All Core Web Vitals in green
    • 75th percentile meets thresholds
    • Mobile and desktop both passing
  3. Monitor in production:

    • Real user metrics stay within budgets
    • No performance regressions
    • Alerts not triggering
  4. Test budget enforcement:

    • Intentionally add heavy resource
    • CI/CD should fail
    • Budget violation detected

Common Mistakes

  1. Setting unrealistic budgets - Too strict budgets that can't be met
  2. Ignoring real user data - Only using lab data
  3. Not enforcing budgets - Setting but not monitoring
  4. One-size-fits-all budgets - Same budget for all page types
  5. Forgetting third-party impact - Not budgeting for ads, analytics
  6. No budget for CLS - Only focusing on loading metrics
  7. Not updating budgets - Keeping outdated thresholds
  8. Missing mobile budgets - Only testing desktop
  9. No component-level budgets - Only page-level tracking
  10. Lack of team buy-in - Budgets not part of culture

Additional Resources

// SYS.FOOTER