Skip to main content

5 - Performance Budgets and Lighthouse CI in Pipelines

How to integrate Lighthouse CI in pipelines to automatically monitor Core Web Vitals, bundle size and performance budgets.

What do you think about this article?

At a Glance

How to integrate Lighthouse CI in pipelines to automatically monitor Core Web Vitals, bundle size and performance budgets.

Introduction: Performance as a Requirement, Not an Afterthought

Web performance is not something to optimize "when there is time." Every millisecond of loading delay translates to lost users: according to Google, 53% of mobile users abandon a site if loading takes more than 3 seconds. Performance budgets transform performance from a vague goal into a measurable, automated constraint in the CI/CD pipeline.

In this article we will configure a complete performance monitoring system: from defining budgets in angular.json to integrating Lighthouse CI into the GitHub Actions pipeline, with thresholds that block deployment if metrics do not meet standards.

What You Will Learn in This Article

  • What performance budgets are and why they matter
  • How to configure budgets in angular.json
  • How to install and configure Lighthouse CI (lhci)
  • The structure of the lighthouserc.js file
  • How to define assertions and quality thresholds
  • How to integrate Lighthouse CI with GitHub Actions
  • How to track scores over time
  • How to monitor bundle size
  • Comparison with SpeedCurve and Calibre
  • How to fail builds on regressions

1. What Are Performance Budgets

A performance budget is a predefined maximum limit for one or more performance metrics. When the budget is exceeded, the build fails, preventing deployment of code that degrades the user experience. The most common budgets are:

Performance Budget Metrics

Metric Description Recommended Budget
Bundle size Total size of JavaScript and CSS files Initial JS < 300 KB (gzip)
LCP Largest Contentful Paint: time to render the largest element < 2.5 seconds
FID/INP Interaction to Next Paint: interface responsiveness < 200 ms
CLS Cumulative Layout Shift: visual stability during loading < 0.1
FCP First Contentful Paint: first visible content < 1.8 seconds
TTI Time to Interactive: when the page becomes interactive < 3.8 seconds

2. Performance Budgets in angular.json

Angular offers a built-in budget system that checks bundle sizes at build time. If a budget is exceeded, the build generates a warning or error.


{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "400kb",
                  "maximumError": "500kb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "4kb",
                  "maximumError": "8kb"
                },
                {
                  "type": "anyScript",
                  "maximumWarning": "150kb",
                  "maximumError": "200kb"
                },
                {
                  "type": "any",
                  "maximumWarning": "300kb",
                  "maximumError": "500kb"
                }
              ]
            }
          }
        }
      }
    }
  }
}
      

Angular Budget Types

Type Description
initial Initial bundle size (main + polyfills + styles)
anyComponentStyle Size of any single component CSS
anyScript Size of any single JavaScript file
any Size of any single file in the bundle
allScript Total size of all JavaScript files
all Total size of all files in the bundle
bundle Size of a specific bundle (by name)

3. Lighthouse CI Setup

Lighthouse CI (LHCI) is Google's official tool for automating Lighthouse audits in CI/CD pipelines. It provides scores for performance, accessibility, best practices, and SEO.


# Install Lighthouse CI
npm install -D @lhci/cli

# Or globally
npm install -g @lhci/cli

# Verify the installation
lhci --version

# Run a local audit
lhci autorun
      

lighthouserc.js Configuration

The lighthouserc.js file in the project root configures Lighthouse CI behavior. Here is a complete configuration for an Angular project:


// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      // How many times to run the audit (for stable results)
      numberOfRuns: 3,

      // URLs to test (after starting the server)
      url: ['http://localhost:4200/'],

      // Start the server before testing
      startServerCommand: 'npx ng serve --configuration=production',
      startServerReadyPattern: 'Angular Live Development Server',
      startServerReadyTimeout: 30000,

      // Chrome settings
      settings: {
        chromeFlags: '--no-sandbox --headless --disable-gpu',
        // Simulate a 4G connection
        throttling: {
          rttMs: 150,
          throughputKbps: 1638.4,
          cpuSlowdownMultiplier: 4,
        },
      },
    },

    assert: {
      assertions: {
        // Performance
        'categories:performance': ['error', { minScore: 0.9 }],
        'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        'total-blocking-time': ['warn', { maxNumericValue: 300 }],
        'speed-index': ['warn', { maxNumericValue: 3000 }],

        // Accessibility
        'categories:accessibility': ['error', { minScore: 0.9 }],

        // Best Practices
        'categories:best-practices': ['warn', { minScore: 0.9 }],

        // SEO
        'categories:seo': ['error', { minScore: 0.9 }],

        // Specific audits
        'uses-text-compression': 'error',
        'uses-responsive-images': 'warn',
        'render-blocking-resources': 'warn',
        'unused-javascript': 'warn',
      },
    },

    upload: {
      // Save results as temporary files
      target: 'temporary-public-storage',
    },
  },
};
      

Difference Between Error and Warn in Assertions

Assertions with error level fail the build, preventing deployment. Assertions with warn level generate a warning but do not block the pipeline. Use error for critical metrics (LCP, CLS) and warn for metrics to monitor (speed-index, unoptimized resources).

4. Integration with GitHub Actions

Here is the complete workflow for running Lighthouse CI in the GitHub Actions pipeline:


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

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

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

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build production
        run: npx ng build --configuration=production

      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v12
        with:
          configPath: './lighthouserc.js'
          uploadArtifacts: true
          temporaryPublicStorage: true

      - name: Upload Lighthouse reports
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: lighthouse-reports
          path: .lighthouseci/
          retention-days: 7
      

Alternative Configuration with Static Server

For more reliable results, it is preferable to serve the production build with a static server instead of using ng serve:


// lighthouserc.js - with static server
module.exports = {
  ci: {
    collect: {
      numberOfRuns: 5,
      url: ['http://localhost:8080/'],
      startServerCommand: 'npx http-server dist/my-app/browser -p 8080 -c-1',
      startServerReadyPattern: 'Available on',
      startServerReadyTimeout: 10000,
      settings: {
        chromeFlags: '--no-sandbox --headless --disable-gpu',
        preset: 'desktop',  // or 'perf' for mobile simulation
      },
    },
    assert: {
      preset: 'lighthouse:recommended',
      assertions: {
        'categories:performance': ['error', { minScore: 0.9, aggregationMethod: 'median-run' }],
        'categories:accessibility': ['error', { minScore: 0.9 }],
        'categories:best-practices': ['warn', { minScore: 0.85 }],
        'categories:seo': ['error', { minScore: 0.9 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};
      

5. Tracking Scores Over Time

To monitor performance evolution, it is useful to save historical scores. Lighthouse CI supports a dedicated server for this purpose, or we can use a simpler approach with GitHub Actions artifacts:


  - name: Extract Lighthouse scores
    id: lh-scores
    run: |
      # Extract scores from the JSON report
      REPORT=$(find .lighthouseci -name 'lhr-*.json' | head -1)
      PERF=$(jq '.categories.performance.score * 100' "$REPORT")
      A11Y=$(jq '.categories.accessibility.score * 100' "$REPORT")
      BP=$(jq '.categories["best-practices"].score * 100' "$REPORT")
      SEO=$(jq '.categories.seo.score * 100' "$REPORT")

      echo "performance=$PERF" >> "$GITHUB_OUTPUT"
      echo "accessibility=$A11Y" >> "$GITHUB_OUTPUT"
      echo "best-practices=$BP" >> "$GITHUB_OUTPUT"
      echo "seo=$SEO" >> "$GITHUB_OUTPUT"

  - name: Comment PR with scores
    if: github.event_name == 'pull_request'
    uses: actions/github-script@v7
    with:
      script: |
        const perf = '${{ steps.lh-scores.outputs.performance }}';
        const a11y = '${{ steps.lh-scores.outputs.accessibility }}';
        const bp = '${{ steps.lh-scores.outputs.best-practices }}';
        const seo = '${{ steps.lh-scores.outputs.seo }}';
        const body = `## Lighthouse Scores
        | Category | Score |
        |----------|-------|
        | Performance | ${perf}/100 |
        | Accessibility | ${a11y}/100 |
        | Best Practices | ${bp}/100 |
        | SEO | ${seo}/100 |`;
        github.rest.issues.createComment({
          issue_number: context.issue.number,
          owner: context.repo.owner,
          repo: context.repo.repo,
          body: body
        });
      

6. Bundle Size Monitoring

Beyond Lighthouse, it is important to monitor bundle size over time. A gradual increase is often imperceptible until it becomes a serious problem.


# Step to monitor bundle size
  - name: Analyze bundle size
    run: |
      # Calculate total bundle size
      TOTAL=$(find dist/my-app/browser -name '*.js' -o -name '*.css' | \
        xargs wc -c | tail -1 | awk '{print $1}')
      TOTAL_KB=$((TOTAL / 1024))

      echo "Total bundle: ${TOTAL_KB} KB"

      # Calculate gzipped size
      GZIP_TOTAL=0
      for file in $(find dist/my-app/browser -name '*.js' -o -name '*.css'); do
        SIZE=$(gzip -c "$file" | wc -c)
        GZIP_TOTAL=$((GZIP_TOTAL + SIZE))
      done
      GZIP_KB=$((GZIP_TOTAL / 1024))

      echo "Gzipped bundle: ${GZIP_KB} KB"

      # Check the budget
      MAX_GZIP_KB=400
      if [ "$GZIP_KB" -gt "$MAX_GZIP_KB" ]; then
        echo "ERROR: Gzipped bundle (${GZIP_KB} KB) exceeds budget (${MAX_GZIP_KB} KB)"
        exit 1
      fi
      

Visual Bundle Analyzer

To understand what takes up space in the bundle, Angular provides a built-in visual analyzer:


# Generate bundle statistics
npx ng build --configuration=production --stats-json

# Analyze with webpack-bundle-analyzer
npx webpack-bundle-analyzer dist/my-app/browser/stats.json

# Alternative: source-map-explorer for more detailed analysis
npx source-map-explorer dist/my-app/browser/main.*.js
      

7. Comparison with Alternative Tools

Performance Monitoring Tools

Tool Type Cost CI Integration Ideal For
Lighthouse CI Open source Free Native All projects
SpeedCurve SaaS From $20/month API Continuous monitoring
Calibre SaaS From $45/month API Enterprise teams
Bundlewatch Open source Free GitHub App Bundle size only
size-limit Open source Free npm script Quick size checks

8. Failing Builds on Regressions

The most robust configuration combines Angular budgets with Lighthouse CI to create a double barrier against performance regressions:


# Complete pipeline with performance gates
name: Performance Pipeline

on:
  pull_request:
    branches: [main]

jobs:
  build-check:
    name: Build and Budget Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci

      # Gate 1: Angular budget check (during the build)
      - name: Build with budget check
        run: npx ng build --configuration=production
        # Build fails if budgets in angular.json are exceeded

  lighthouse-check:
    name: Lighthouse Performance Audit
    runs-on: ubuntu-latest
    needs: build-check
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npx ng build --configuration=production

      # Gate 2: Lighthouse audit (runtime metrics)
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v12
        with:
          configPath: './lighthouserc.js'
          uploadArtifacts: true
          temporaryPublicStorage: true
      

Gradual Implementation Strategy

  • Week 1: Run Lighthouse CI in warn mode to collect a baseline
  • Week 2: Set thresholds based on the baseline (average - 5%)
  • Week 3: Move critical metrics (LCP, CLS) to error
  • Week 4: Activate branch protection rules to require the Lighthouse check
  • Ongoing: Gradually tighten thresholds as the team optimizes

Conclusion

In this article we built a complete performance monitoring system in the CI/CD pipeline. We configured Angular budgets for bundle size control, integrated Lighthouse CI for automatic audits on every Pull Request, and implemented score reporting directly in PR comments.

The key message is that performance must be treated like any other project requirement: defined, measured, and automatically verified. With budgets and Lighthouse CI in the pipeline, every regression is caught before reaching production.

In the final article of the series we will tackle monitoring and alerting for web applications: error tracking with Sentry, real user monitoring, and strategies for maintaining quality even after deployment.

Frequently asked questions

What is a performance budget in the context of web development?

A performance budget is a predefined maximum limit for one or more performance metrics, which helps prevent deployment of code that degrades the user experience.

How does Angular's built-in budget system work?

Angular's built-in budget system checks bundle sizes at build time and generates a warning or error if a budget is exceeded.

What is Lighthouse CI and how is it used in the CI/CD pipeline?

Lighthouse CI is Google's official tool for automating Lighthouse audits in CI/CD pipelines, providing scores for performance, accessibility, best practices, and SEO.

Why is it recommended to serve the production build with a static server instead of using ?

It is recommended to serve the production build with a static server for more reliable results.

Sources and further reading

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects