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.jsfile - 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
warnmode 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.
Related deep dive: 4 - Gestione dei Segreti nelle Pipeline CI/CD.
Related deep dive: 6 - Monitoraggio e Alerting per Applicazioni Web.
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.
Commenti
Caricamento commenti...
Sign in to comment