Website Performance Budget Implementation: A Developer's Guide
A practical Fusebox guide to website performance budget implementation.
Website Performance Budget Implementation: A Developer's Guide
Performance budgets are the speed limits of web development—they keep your site fast by setting hard limits on what you can add. Amazon found that every 100ms of latency cost them 1% in sales. Google discovered that 53% of mobile users abandon sites that take longer than 3 seconds to load. A performance budget ensures your site stays fast as it grows.
Why Performance Budgets Matter
In 2019, a major e-commerce site's Black Friday campaign failed when their homepage ballooned to 8MB after adding "just one more" marketing banner. Load times exceeded 15 seconds on mobile, resulting in a 60% drop in conversions. Performance budgets prevent this death by a thousand cuts—each small addition that eventually kills your site's speed.
Performance Budget Calculator
Comprehensive budget calculation and tracking:
// Performance Budget Calculator
class PerformanceBudgetCalculator {
constructor() {
this.budgets = {
// Size budgets (in bytes)
size: {
html: 50 * 1024, // 50KB
css: 100 * 1024, // 100KB
js: 300 * 1024, // 300KB
images: 500 * 1024, // 500KB per image
fonts: 100 * 1024, // 100KB
total: 1500 * 1024 // 1.5MB total
},
// Count budgets
count: {
requests: 50,
domains: 10,
cssFiles: 3,
jsFiles: 10,
fonts: 5
},
// Timing budgets (in ms)
timing: {
ttfb: 600, // Time to First Byte
fcp: 1800, // First Contentful Paint
lcp: 2500, // Largest Contentful Paint
fid: 100, // First Input Delay
cls: 0.1, // Cumulative Layout Shift
tti: 3800, // Time to Interactive
totalBlockingTime: 300
},
// Custom metrics
custom: {
heroImageLoad: 1000,
searchResults: 500,
cartUpdate: 200
}
};
this.measurements = {};
this.violations = [];
}
// Measure current performance
async measureCurrentPerformance() {
const measurements = {
size: await this.measureSizes(),
count: this.measureCounts(),
timing: await this.measureTimings(),
custom: await this.measureCustomMetrics()
};
this.measurements = measurements;
this.checkBudgets();
return measurements;
}
// Measure resource sizes
async measureSizes() {
const sizes = {
html: 0,
css: 0,
js: 0,
images: 0,
fonts: 0,
total: 0,
breakdown: []
};
// Get resource timing data
const resources = performance.getEntriesByType('resource');
const navigation = performance.getEntriesByType('navigation')[0];
// Add HTML size
if (navigation) {
sizes.html = navigation.transferSize || 0;
sizes.total += sizes.html;
}
// Categorize resources
resources.forEach(resource => {
const size = resource.transferSize || 0;
const name = resource.name;
let category = 'other';
if (name.match(/\.css(\?|$)/i)) {
category = 'css';
sizes.css += size;
} else if (name.match(/\.js(\?|$)/i)) {
category = 'js';
sizes.js += size;
} else if (name.match(/\.(jpg|jpeg|png|gif|webp|svg|ico)(\?|$)/i)) {
category = 'images';
sizes.images += size;
} else if (name.match(/\.(woff|woff2|ttf|otf|eot)(\?|$)/i)) {
category = 'fonts';
sizes.fonts += size;
}
sizes.total += size;
sizes.breakdown.push({
url: name,
category,
size,
compressed: resource.encodedBodySize || 0,
duration: resource.duration
});
});
// Sort breakdown by size
sizes.breakdown.sort((a, b) => b.size - a.size);
return sizes;
}
// Measure resource counts
measureCounts() {
const counts = {
requests: 0,
domains: new Set(),
cssFiles: 0,
jsFiles: 0,
fonts: 0,
breakdown: {}
};
const resources = performance.getEntriesByType('resource');
counts.requests = resources.length + 1; // +1 for HTML
resources.forEach(resource => {
const url = new URL(resource.name);
counts.domains.add(url.hostname);
if (resource.name.match(/\.css(\?|$)/i)) {
counts.cssFiles++;
} else if (resource.name.match(/\.js(\?|$)/i)) {
counts.jsFiles++;
} else if (resource.name.match(/\.(woff|woff2|ttf|otf|eot)(\?|$)/i)) {
counts.fonts++;
}
});
counts.domains = counts.domains.size;
// Group by domain
const domainCounts = {};
resources.forEach(resource => {
const hostname = new URL(resource.name).hostname;
domainCounts[hostname] = (domainCounts[hostname] || 0) + 1;
});
counts.breakdown = domainCounts;
return counts;
}
// Measure performance timings
async measureTimings() {
const timings = {
ttfb: 0,
fcp: 0,
lcp: 0,
fid: 0,
cls: 0,
tti: 0,
totalBlockingTime: 0
};
// Get navigation timing
const navigation = performance.getEntriesByType('navigation')[0];
if (navigation) {
timings.ttfb = navigation.responseStart - navigation.requestStart;
}
// Get paint timing
const paintEntries = performance.getEntriesByType('paint');
paintEntries.forEach(entry => {
if (entry.name === 'first-contentful-paint') {
timings.fcp = entry.startTime;
}
});
// Get Web Vitals
try {
// LCP
const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
if (lcpEntries.length > 0) {
timings.lcp = lcpEntries[lcpEntries.length - 1].startTime;
}
// CLS
let clsValue = 0;
const clsEntries = performance.getEntriesByType('layout-shift');
clsEntries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
timings.cls = clsValue;
// Estimate TTI (simplified)
timings.tti = timings.fcp + 2000; // Rough estimate
} catch (e) {
console.log('Some Web Vitals not available');
}
return timings;
}
// Measure custom metrics
async measureCustomMetrics() {
const custom = {};
// Example: Measure hero image load time
const heroImage = document.querySelector('[data-hero-image]');
if (heroImage && heroImage.complete) {
const imageEntry = performance.getEntriesByName(heroImage.src)[0];
if (imageEntry) {
custom.heroImageLoad = imageEntry.responseEnd;
}
}
// Add your custom metrics here
return custom;
}
// Check budgets against measurements
checkBudgets() {
this.violations = [];
// Check size budgets
Object.entries(this.budgets.size).forEach(([key, budget]) => {
const actual = this.measurements.size[key];
if (actual > budget) {
this.violations.push({
type: 'size',
metric: key,
budget,
actual,
overBy: actual - budget,
percentage: ((actual - budget) / budget * 100).toFixed(2)
});
}
});
// Check count budgets
Object.entries(this.budgets.count).forEach(([key, budget]) => {
const actual = this.measurements.count[key];
if (actual > budget) {
this.violations.push({
type: 'count',
metric: key,
budget,
actual,
overBy: actual - budget,
percentage: ((actual - budget) / budget * 100).toFixed(2)
});
}
});
// Check timing budgets
Object.entries(this.budgets.timing).forEach(([key, budget]) => {
const actual = this.measurements.timing[key];
if (actual > budget) {
this.violations.push({
type: 'timing',
metric: key,
budget,
actual,
overBy: actual - budget,
percentage: ((actual - budget) / budget * 100).toFixed(2)
});
}
});
return this.violations;
}
// Generate report
generateReport() {
const report = {
timestamp: new Date().toISOString(),
summary: {
totalViolations: this.violations.length,
criticalViolations: this.violations.filter(v => v.percentage > 50).length,
score: this.calculateScore()
},
measurements: this.measurements,
violations: this.violations,
recommendations: this.generateRecommendations()
};
return report;
}
// Calculate performance score
calculateScore() {
if (this.violations.length === 0) return 100;
let score = 100;
this.violations.forEach(violation => {
const penalty = Math.min(20, parseFloat(violation.percentage) / 5);
score -= penalty;
});
return Math.max(0, Math.round(score));
}
// Generate recommendations
generateRecommendations() {
const recommendations = [];
// Size recommendations
if (this.measurements.size.js > this.budgets.size.js) {
recommendations.push({
priority: 'high',
type: 'javascript',
issue: 'JavaScript bundle too large',
suggestions: [
'Implement code splitting',
'Remove unused dependencies',
'Use dynamic imports for non-critical code',
'Minify and compress JavaScript files'
],
potential: `Could save ${this.formatBytes(this.measurements.size.js - this.budgets.size.js)}`
});
}
if (this.measurements.size.images > this.budgets.size.images) {
recommendations.push({
priority: 'high',
type: 'images',
issue: 'Images too large',
suggestions: [
'Use modern formats (WebP, AVIF)',
'Implement responsive images',
'Lazy load below-the-fold images',
'Optimize image compression'
]
});
}
// Timing recommendations
if (this.measurements.timing.lcp > this.budgets.timing.lcp) {
recommendations.push({
priority: 'critical',
type: 'lcp',
issue: 'Largest Contentful Paint too slow',
suggestions: [
'Optimize server response time',
'Preload critical resources',
'Reduce render-blocking resources',
'Optimize images and fonts'
]
});
}
return recommendations;
}
// Format bytes
formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
// Usage
const budgetCalculator = new PerformanceBudgetCalculator();
budgetCalculator.measureCurrentPerformance().then(measurements => {
console.log('Performance Report:', budgetCalculator.generateReport());
});
Budget Enforcement System
Enforce budgets in your build process:
// Performance Budget Enforcer
class PerformanceBudgetEnforcer {
constructor(budgetConfig) {
this.config = budgetConfig;
this.results = {
passed: true,
violations: [],
warnings: []
};
}
// Check bundle sizes
async checkBundleSizes(bundles) {
const violations = [];
bundles.forEach(bundle => {
const budget = this.config.bundles[bundle.name] || this.config.bundles.default;
if (bundle.size > budget.error) {
violations.push({
severity: 'error',
bundle: bundle.name,
size: bundle.size,
limit: budget.error,
message: `Bundle ${bundle.name} exceeds error threshold`
});
this.results.passed = false;
} else if (bundle.size > budget.warning) {
this.results.warnings.push({
severity: 'warning',
bundle: bundle.name,
size: bundle.size,
limit: budget.warning,
message: `Bundle ${bundle.name} exceeds warning threshold`
});
}
});
this.results.violations.push(...violations);
return violations.length === 0;
}
// Webpack plugin for budget enforcement
createWebpackPlugin() {
const enforcer = this;
return class PerformanceBudgetPlugin {
apply(compiler) {
compiler.hooks.afterEmit.tapAsync('PerformanceBudgetPlugin', (compilation, callback) => {
const bundles = [];
for (const [name, asset] of compilation.assets) {
bundles.push({
name,
size: asset.size()
});
}
enforcer.checkBundleSizes(bundles).then(passed => {
if (!passed) {
compilation.errors.push(new Error(
'Performance budget exceeded!\n' +
enforcer.results.violations.map(v => v.message).join('\n')
));
}
enforcer.results.warnings.forEach(warning => {
compilation.warnings.push(new Error(warning.message));
});
callback();
});
});
}
};
}
// CI/CD integration
generateCIScript() {
return `
#!/bin/bash
# Performance Budget Check
echo "Checking performance budgets..."
# Run Lighthouse CI
npm install -g @lhci/cli
lhci autorun --config=lighthouserc.json
# Check bundle sizes
BUNDLE_SIZES=$(node -e "
const fs = require('fs');
const path = require('path');
const distDir = './dist';
const bundles = fs.readdirSync(distDir)
.filter(file => file.endsWith('.js'))
.map(file => ({
name: file,
size: fs.statSync(path.join(distDir, file)).size
}));
console.log(JSON.stringify(bundles));
")
# Run budget check
node -e "
const bundles = $BUNDLE_SIZES;
const budgets = ${JSON.stringify(this.config.bundles)};
let failed = false;
bundles.forEach(bundle => {
const budget = budgets[bundle.name] || budgets.default;
if (bundle.size > budget.error) {
console.error(\`❌ \${bundle.name}: \${bundle.size} bytes (limit: \${budget.error})\`);
failed = true;
} else if (bundle.size > budget.warning) {
console.warn(\`⚠️ \${bundle.name}: \${bundle.size} bytes (warning: \${budget.warning})\`);
} else {
console.log(\`✅ \${bundle.name}: \${bundle.size} bytes\`);
}
});
if (failed) {
process.exit(1);
}
"`;
}
// Lighthouse configuration
generateLighthouseConfig() {
return {
ci: {
collect: {
numberOfRuns: 3,
url: ['http://localhost:3000']
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: this.config.metrics.fcp }],
'largest-contentful-paint': ['error', { maxNumericValue: this.config.metrics.lcp }],
'cumulative-layout-shift': ['error', { maxNumericValue: this.config.metrics.cls }],
'total-blocking-time': ['error', { maxNumericValue: this.config.metrics.tbt }],
'resource-summary:script:size': ['error', { maxNumericValue: this.config.resources.js }],
'resource-summary:stylesheet:size': ['error', { maxNumericValue: this.config.resources.css }],
'resource-summary:image:size': ['error', { maxNumericValue: this.config.resources.images }],
'resource-summary:total:size': ['error', { maxNumericValue: this.config.resources.total }]
}
},
upload: {
target: 'temporary-public-storage'
}
}
};
}
}
// Example configuration
const budgetConfig = {
bundles: {
'main.js': { warning: 200 * 1024, error: 250 * 1024 },
'vendor.js': { warning: 300 * 1024, error: 400 * 1024 },
default: { warning: 100 * 1024, error: 150 * 1024 }
},
metrics: {
fcp: 1800,
lcp: 2500,
cls: 0.1,
tbt: 300
},
resources: {
js: 300 * 1024,
css: 100 * 1024,
images: 500 * 1024,
total: 1500 * 1024
}
};
const enforcer = new PerformanceBudgetEnforcer(budgetConfig);
Real-time Budget Monitor
Monitor performance budgets in real-time:
// Real-time Performance Budget Monitor
class RealTimeBudgetMonitor {
constructor(budgets) {
this.budgets = budgets;
this.metrics = {
resources: new Map(),
timings: {},
violations: []
};
this.observers = {};
this.callbacks = [];
}
// Start monitoring
start() {
this.setupResourceObserver();
this.setupLayoutShiftObserver();
this.setupLongTaskObserver();
this.monitorMemory();
console.log('Performance budget monitoring started');
}
// Monitor resource loading
setupResourceObserver() {
this.observers.resource = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.trackResource(entry);
}
});
this.observers.resource.observe({ entryTypes: ['resource'] });
}
// Track individual resources
trackResource(entry) {
const size = entry.transferSize || 0;
const type = this.getResourceType(entry.name);
// Update totals
if (!this.metrics.resources.has(type)) {
this.metrics.resources.set(type, {
count: 0,
totalSize: 0,
items: []
});
}
const typeMetrics = this.metrics.resources.get(type);
typeMetrics.count++;
typeMetrics.totalSize += size;
typeMetrics.items.push({
url: entry.name,
size,
duration: entry.duration
});
// Check budget
if (this.budgets.size[type] && typeMetrics.totalSize > this.budgets.size[type]) {
this.addViolation({
type: 'size',
category: type,
budget: this.budgets.size[type],
actual: typeMetrics.totalSize,
resource: entry.name
});
}
}
// Monitor layout shifts
setupLayoutShiftObserver() {
let clsValue = 0;
this.observers.layoutShift = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
if (clsValue > this.budgets.timing.cls) {
this.addViolation({
type: 'timing',
metric: 'cls',
budget: this.budgets.timing.cls,
actual: clsValue
});
}
}
}
});
try {
this.observers.layoutShift.observe({ entryTypes: ['layout-shift'] });
} catch (e) {
console.log('Layout shift observer not supported');
}
}
// Monitor long tasks
setupLongTaskObserver() {
let totalBlockingTime = 0;
this.observers.longTask = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const blockingTime = entry.duration - 50; // Tasks over 50ms are blocking
if (blockingTime > 0) {
totalBlockingTime += blockingTime;
if (totalBlockingTime > this.budgets.timing.totalBlockingTime) {
this.addViolation({
type: 'timing',
metric: 'totalBlockingTime',
budget: this.budgets.timing.totalBlockingTime,
actual: totalBlockingTime
});
}
}
}
});
try {
this.observers.longTask.observe({ entryTypes: ['longtask'] });
} catch (e) {
console.log('Long task observer not supported');
}
}
// Monitor memory usage
monitorMemory() {
if (!performance.memory) return;
setInterval(() => {
const memoryMB = performance.memory.usedJSHeapSize / (1024 * 1024);
if (this.budgets.memory && memoryMB > this.budgets.memory) {
this.addViolation({
type: 'memory',
budget: this.budgets.memory,
actual: memoryMB
});
}
}, 5000);
}
// Add violation
addViolation(violation) {
violation.timestamp = new Date().toISOString();
this.metrics.violations.push(violation);
// Notify callbacks
this.callbacks.forEach(callback => {
callback('violation', violation);
});
console.warn('Performance budget violation:', violation);
}
// Get resource type
getResourceType(url) {
if (url.match(/\.css(\?|$)/i)) return 'css';
if (url.match(/\.js(\?|$)/i)) return 'js';
if (url.match(/\.(jpg|jpeg|png|gif|webp|svg)(\?|$)/i)) return 'images';
if (url.match(/\.(woff|woff2|ttf|otf|eot)(\?|$)/i)) return 'fonts';
return 'other';
}
// Subscribe to events
subscribe(callback) {
this.callbacks.push(callback);
}
// Get current status
getStatus() {
const status = {
resources: {},
violations: this.metrics.violations,
score: 100
};
// Convert resource map to object
for (const [type, data] of this.metrics.resources) {
status.resources[type] = {
count: data.count,
totalSize: data.totalSize,
budget: this.budgets.size[type] || null,
withinBudget: !this.budgets.size[type] || data.totalSize <= this.budgets.size[type]
};
}
// Calculate score
status.score = Math.max(0, 100 - (this.metrics.violations.length * 10));
return status;
}
// Stop monitoring
stop() {
Object.values(this.observers).forEach(observer => observer.disconnect());
console.log('Performance budget monitoring stopped');
}
}
// Usage
const monitor = new RealTimeBudgetMonitor({
size: {
js: 300 * 1024,
css: 100 * 1024,
images: 500 * 1024,
fonts: 100 * 1024
},
timing: {
cls: 0.1,
totalBlockingTime: 300
},
memory: 50 // MB
});
monitor.subscribe((event, data) => {
console.log(`Budget ${event}:`, data);
});
monitor.start();
Budget Visualization Dashboard
Create a visual dashboard for performance budgets:
// Performance Budget Dashboard
class PerformanceBudgetDashboard {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.data = {
budgets: {},
current: {},
history: []
};
}
// Initialize dashboard
init(budgets) {
this.data.budgets = budgets;
this.createDashboard();
this.startMonitoring();
}
// Create dashboard HTML
createDashboard() {
this.container.innerHTML = `
<div class="perf-dashboard">
<h2>Performance Budget Dashboard</h2>
<div class="perf-summary">
<div class="perf-score">
<h3>Score</h3>
<div class="score-value">100</div>
</div>
<div class="perf-violations">
<h3>Violations</h3>
<div class="violations-count">0</div>
</div>
</div>
<div class="perf-metrics">
<h3>Resource Budgets</h3>
<div id="resource-meters"></div>
</div>
<div class="perf-timing">
<h3>Timing Budgets</h3>
<div id="timing-meters"></div>
</div>
<div class="perf-history">
<h3>History</h3>
<canvas id="history-chart"></canvas>
</div>
</div>
<style>
.perf-dashboard {
font-family: system-ui, -apple-system, sans-serif;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
}
.perf-summary {
display: flex;
gap: 20px;
margin-bottom: 30px;
}
.perf-score, .perf-violations {
background: white;
padding: 20px;
border-radius: 8px;
text-align: center;
flex: 1;
}
.score-value {
font-size: 48px;
font-weight: bold;
color: #4CAF50;
}
.violations-count {
font-size: 48px;
font-weight: bold;
color: #f44336;
}
.perf-meter {
margin: 10px 0;
background: white;
padding: 15px;
border-radius: 8px;
}
.meter-label {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.meter-bar {
height: 20px;
background: #e0e0e0;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.meter-fill {
height: 100%;
background: #4CAF50;
transition: width 0.3s ease;
}
.meter-fill.warning {
background: #ff9800;
}
.meter-fill.error {
background: #f44336;
}
.meter-limit {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
background: #333;
}
</style>
`;
}
// Create meter element
createMeter(label, current, budget, unit = 'KB') {
const percentage = (current / budget) * 100;
const status = percentage > 100 ? 'error' : percentage > 80 ? 'warning' : '';
return `
<div class="perf-meter">
<div class="meter-label">
<span>${label}</span>
<span>${this.formatValue(current, unit)} / ${this.formatValue(budget, unit)}</span>
</div>
<div class="meter-bar">
<div class="meter-fill ${status}" style="width: ${Math.min(percentage, 100)}%"></div>
<div class="meter-limit" style="left: 100%"></div>
</div>
</div>
`;
}
// Format values
formatValue(value, unit) {
if (unit === 'KB') {
return (value / 1024).toFixed(1) + ' KB';
} else if (unit === 'ms') {
return value.toFixed(0) + ' ms';
}
return value.toFixed(2) + ' ' + unit;
}
// Update dashboard
updateDashboard(data) {
// Update score
const scoreElement = this.container.querySelector('.score-value');
scoreElement.textContent = data.score;
scoreElement.style.color = data.score >= 80 ? '#4CAF50' :
data.score >= 60 ? '#ff9800' : '#f44336';
// Update violations
this.container.querySelector('.violations-count').textContent = data.violations;
// Update resource meters
const resourceMeters = this.container.querySelector('#resource-meters');
resourceMeters.innerHTML = Object.entries(data.resources).map(([type, values]) =>
this.createMeter(
type.charAt(0).toUpperCase() + type.slice(1),
values.current,
values.budget,
'KB'
)
).join('');
// Update timing meters
const timingMeters = this.container.querySelector('#timing-meters');
timingMeters.innerHTML = Object.entries(data.timing).map(([metric, values]) =>
this.createMeter(
metric.toUpperCase(),
values.current,
values.budget,
metric === 'cls' ? '' : 'ms'
)
).join('');
// Update history
this.updateHistory(data);
}
// Update history chart
updateHistory(data) {
this.data.history.push({
timestamp: Date.now(),
score: data.score
});
// Keep last 50 data points
if (this.data.history.length > 50) {
this.data.history.shift();
}
// Draw chart (simplified)
const canvas = this.container.querySelector('#history-chart');
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth;
canvas.height = 150;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid
ctx.strokeStyle = '#e0e0e0';
ctx.beginPath();
for (let i = 0; i <= 10; i++) {
const y = (i / 10) * canvas.height;
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
// Draw line
ctx.strokeStyle = '#4CAF50';
ctx.lineWidth = 2;
ctx.beginPath();
this.data.history.forEach((point, index) => {
const x = (index / (this.data.history.length - 1)) * canvas.width;
const y = canvas.height - (point.score / 100) * canvas.height;
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
}
// Start monitoring
startMonitoring() {
// Simulate data updates
setInterval(() => {
const mockData = {
score: Math.max(0, 100 - Math.random() * 30),
violations: Math.floor(Math.random() * 5),
resources: {
js: {
current: 250 * 1024 + Math.random() * 100 * 1024,
budget: 300 * 1024
},
css: {
current: 80 * 1024 + Math.random() * 40 * 1024,
budget: 100 * 1024
},
images: {
current: 400 * 1024 + Math.random() * 200 * 1024,
budget: 500 * 1024
}
},
timing: {
lcp: {
current: 2000 + Math.random() * 1000,
budget: 2500
},
fid: {
current: 50 + Math.random() * 100,
budget: 100
},
cls: {
current: Math.random() * 0.2,
budget: 0.1
}
}
};
this.updateDashboard(mockData);
}, 2000);
}
}
// Usage
// const dashboard = new PerformanceBudgetDashboard('dashboard-container');
// dashboard.init(budgetConfig);
Best Practices for Performance Budgets
- Start Conservative: Begin with achievable budgets and tighten over time
- Measure Real Users: Use RUM data to set realistic targets
- Budget by Journey: Different budgets for different user paths
- Include All Resources: Don't forget third-party scripts
- Automate Enforcement: Integrate into CI/CD pipeline
- Monitor Continuously: Track budget violations in production
- Communicate Clearly: Make budgets visible to all stakeholders
- Review Regularly: Adjust budgets based on user needs
- Consider Context: Mobile vs desktop, slow vs fast networks
- Focus on Impact: Prioritize metrics that affect users
Common Budget Violations
- JavaScript Bloat: Unnecessary dependencies and polyfills
- Unoptimized Images: Large images without compression
- Third-Party Scripts: Analytics and advertising overhead
- Web Fonts: Loading too many font variations
- CSS Complexity: Unused styles and large frameworks
- Lazy Loading Failures: Loading everything upfront
- Memory Leaks: Growing memory usage over time
- API Payload Size: Sending too much data
- Render Blocking: Scripts and styles blocking paint
- Cache Misses: Resources not properly cached
Remember: Performance budgets are not about limiting creativity—they're about ensuring your creative vision loads fast enough for users to experience it. Set budgets early, enforce them automatically, and treat performance as a feature, not an afterthought.