PerformanceJanuary 1, 2024ยท 14 min read
Core Web Vitals: Master Performance Metrics That Actually Matter
A practical Fusebox guide to core web vitals.
Core Web Vitals: Master Performance Metrics That Actually Matter
Published: January 2024
Reading time: 10 minutes
Your site might look perfect, but if it feels slow, users leave. Core Web Vitals are Google's answer to measuring real user experience. Here's how to analyze, optimize, and monitor the metrics that directly impact your rankings and conversions.
Why Core Web Vitals Matter
The Business Impact
- 53% of users abandon sites that take >3 seconds
- 1 second delay = 7% reduction in conversions
- Google ranking factor since June 2021
- 70% of sites fail Core Web Vitals assessment
- 2x more engagement for sites meeting all thresholds
Real Performance Failures
Amazon: 100ms delay = 1% sales decrease
Google: 500ms delay = 20% traffic drop
Walmart: 100ms improvement = 1% revenue increase
Pinterest: 40% less wait time = 15% more SEO traffic
BBC: +1 second load time = 10% users lost
Quick Core Web Vitals Check
1. Instant Performance Analysis
// Analyze Core Web Vitals on current page
(function analyzeWebVitals() {
console.log('๐ Core Web Vitals Analysis\n');
// Check if Performance Observer is available
if (!('PerformanceObserver' in window)) {
console.log('โ Performance Observer not supported');
return;
}
const vitals = {
LCP: { value: null, rating: null },
FID: { value: null, rating: null },
CLS: { value: null, rating: null },
FCP: { value: null, rating: null },
TTFB: { value: null, rating: null }
};
// LCP (Largest Contentful Paint)
new PerformanceObserver((entryList) => {
const entries = entryList.getEntries();
const lastEntry = entries[entries.length - 1];
vitals.LCP.value = lastEntry.renderTime || lastEntry.loadTime;
vitals.LCP.rating = vitals.LCP.value <= 2500 ? 'Good' :
vitals.LCP.value <= 4000 ? 'Needs Improvement' : 'Poor';
console.log(`๐จ LCP (Largest Contentful Paint): ${vitals.LCP.value.toFixed(0)}ms - ${vitals.LCP.rating}`);
console.log(` Element: ${lastEntry.element?.tagName || 'Unknown'}`);
}).observe({ entryTypes: ['largest-contentful-paint'] });
// FID (First Input Delay) - Using proxy metric
let firstInputTime = null;
['click', 'keydown', 'touchstart'].forEach(type => {
window.addEventListener(type, function fidHandler(e) {
if (!firstInputTime) {
firstInputTime = performance.now();
const processingStart = e.timeStamp;
const delay = processingStart - firstInputTime;
vitals.FID.value = delay;
vitals.FID.rating = delay <= 100 ? 'Good' :
delay <= 300 ? 'Needs Improvement' : 'Poor';
console.log(`\nโก FID (First Input Delay): ${delay.toFixed(0)}ms - ${vitals.FID.rating}`);
}
window.removeEventListener(type, fidHandler);
}, { once: true });
});
// CLS (Cumulative Layout Shift)
let clsValue = 0;
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
vitals.CLS.value = clsValue;
vitals.CLS.rating = clsValue <= 0.1 ? 'Good' :
clsValue <= 0.25 ? 'Needs Improvement' : 'Poor';
}).observe({ entryTypes: ['layout-shift'] });
// FCP (First Contentful Paint)
new PerformanceObserver((entryList) => {
const [fcpEntry] = entryList.getEntries();
vitals.FCP.value = fcpEntry.startTime;
vitals.FCP.rating = vitals.FCP.value <= 1800 ? 'Good' :
vitals.FCP.value <= 3000 ? 'Needs Improvement' : 'Poor';
console.log(`\n๐๏ธ FCP (First Contentful Paint): ${vitals.FCP.value.toFixed(0)}ms - ${vitals.FCP.rating}`);
}).observe({ entryTypes: ['paint'] });
// TTFB (Time to First Byte)
const navTiming = performance.getEntriesByType('navigation')[0];
if (navTiming) {
vitals.TTFB.value = navTiming.responseStart - navTiming.requestStart;
vitals.TTFB.rating = vitals.TTFB.value <= 800 ? 'Good' :
vitals.TTFB.value <= 1800 ? 'Needs Improvement' : 'Poor';
console.log(`\n๐ก TTFB (Time to First Byte): ${vitals.TTFB.value.toFixed(0)}ms - ${vitals.TTFB.rating}`);
}
// Summary after delay
setTimeout(() => {
console.log(`\n๐ CLS (Cumulative Layout Shift): ${vitals.CLS.value.toFixed(3)} - ${vitals.CLS.rating}`);
console.log('\n๐ Performance Summary:');
console.log('Metric | Value | Status');
console.log('-------|-------|-------');
Object.entries(vitals).forEach(([metric, data]) => {
if (data.value !== null) {
const icon = data.rating === 'Good' ? 'โ
' :
data.rating === 'Needs Improvement' ? '๐ก' : 'โ';
const value = metric === 'CLS' ? data.value.toFixed(3) : `${data.value.toFixed(0)}ms`;
console.log(`${metric.padEnd(6)} | ${value.padEnd(7)} | ${icon} ${data.rating}`);
}
});
}, 3000);
})();
2. Resource Performance Analysis
// Analyze resource loading performance
function analyzeResourcePerformance() {
console.log('\n๐ฆ Resource Performance Analysis\n');
const resources = performance.getEntriesByType('resource');
const resourceTypes = {};
const slowResources = [];
let totalTransferSize = 0;
resources.forEach(resource => {
const type = resource.name.split('.').pop().split('?')[0] || 'other';
if (!resourceTypes[type]) {
resourceTypes[type] = { count: 0, totalDuration: 0, totalSize: 0 };
}
resourceTypes[type].count++;
resourceTypes[type].totalDuration += resource.duration;
resourceTypes[type].totalSize += resource.transferSize || 0;
totalTransferSize += resource.transferSize || 0;
// Track slow resources (>500ms)
if (resource.duration > 500) {
slowResources.push({
name: resource.name.split('/').pop().substring(0, 50),
duration: resource.duration,
size: resource.transferSize || 0,
type: type
});
}
});
// Resource type summary
console.log('๐ Resource Types Summary:');
console.log('Type | Count | Avg Duration | Total Size');
console.log('-----|-------|--------------|------------');
Object.entries(resourceTypes).forEach(([type, stats]) => {
const avgDuration = (stats.totalDuration / stats.count).toFixed(0);
const totalSizeMB = (stats.totalSize / 1024 / 1024).toFixed(2);
console.log(
`${type.padEnd(4)} | ${stats.count.toString().padEnd(5)} | ${avgDuration.padEnd(10)}ms | ${totalSizeMB}MB`
);
});
console.log(`\nTotal transfer size: ${(totalTransferSize / 1024 / 1024).toFixed(2)}MB`);
// Slow resources
if (slowResources.length > 0) {
console.log('\nโ ๏ธ Slow Resources (>500ms):');
slowResources
.sort((a, b) => b.duration - a.duration)
.slice(0, 5)
.forEach(resource => {
console.log(` ${resource.duration.toFixed(0)}ms - ${resource.name} (${(resource.size / 1024).toFixed(0)}KB)`);
});
}
// Critical rendering path
console.log('\n๐ฏ Critical Rendering Path:');
const renderBlockingResources = resources.filter(r =>
(r.name.includes('.css') || r.name.includes('.js')) &&
r.startTime < (performance.timing.domContentLoadedEventStart - performance.timing.navigationStart)
);
console.log(`Render-blocking resources: ${renderBlockingResources.length}`);
renderBlockingResources.slice(0, 3).forEach(resource => {
console.log(` - ${resource.name.split('/').pop()} (${resource.duration.toFixed(0)}ms)`);
});
}
analyzeResourcePerformance();
3. User-Centric Performance Metrics
// Measure user-centric performance metrics
function measureUserMetrics() {
console.log('\n๐ค User-Centric Performance Metrics\n');
const navigation = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByType('paint');
// Time to Interactive (TTI) approximation
const tti = performance.timing.loadEventEnd - performance.timing.navigationStart;
// First Meaningful Paint approximation
const fmp = paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0;
// Speed Index approximation
const speedIndex = (fmp + tti) / 2;
// User timing metrics
const metrics = {
'Time to Interactive (TTI)': tti,
'First Meaningful Paint (FMP)': fmp,
'Speed Index': speedIndex,
'DOM Content Loaded': performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,
'Load Complete': performance.timing.loadEventEnd - performance.timing.navigationStart,
'DOM Interactive': performance.timing.domInteractive - performance.timing.navigationStart,
'DOM Complete': performance.timing.domComplete - performance.timing.navigationStart
};
console.log('๐ Performance Timeline:');
Object.entries(metrics).forEach(([metric, value]) => {
const rating = value < 1000 ? '๐ข' : value < 3000 ? '๐ก' : '๐ด';
console.log(`${rating} ${metric}: ${value.toFixed(0)}ms`);
});
// Connection quality
if (navigator.connection) {
console.log('\n๐ถ Connection Quality:');
console.log(`Type: ${navigator.connection.effectiveType}`);
console.log(`Downlink: ${navigator.connection.downlink}Mbps`);
console.log(`RTT: ${navigator.connection.rtt}ms`);
console.log(`Save Data: ${navigator.connection.saveData ? 'Yes' : 'No'}`);
}
// Memory usage (Chrome only)
if (performance.memory) {
console.log('\n๐พ Memory Usage:');
const used = (performance.memory.usedJSHeapSize / 1048576).toFixed(2);
const total = (performance.memory.totalJSHeapSize / 1048576).toFixed(2);
const limit = (performance.memory.jsHeapSizeLimit / 1048576).toFixed(2);
console.log(`Used: ${used}MB / ${total}MB (Limit: ${limit}MB)`);
}
}
measureUserMetrics();
Performance Optimization Strategies
1. LCP (Largest Contentful Paint) Optimization
// LCP optimization analyzer
function optimizeLCP() {
console.log('\n๐จ LCP Optimization Analysis\n');
// Find LCP element
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
const element = lastEntry.element;
if (element) {
console.log('๐ฏ LCP Element Found:');
console.log(`Tag: ${element.tagName}`);
console.log(`Class: ${element.className || 'none'}`);
console.log(`ID: ${element.id || 'none'}`);
// Optimization suggestions
console.log('\n๐ก Optimization Suggestions:');
if (element.tagName === 'IMG') {
console.log('โข Add loading="eager" to LCP image');
console.log('โข Use <link rel="preload"> for image');
console.log('โข Optimize image format (WebP/AVIF)');
console.log('โข Use responsive images with srcset');
// Check image optimization
if (element.loading === 'lazy') {
console.log('โ ๏ธ LCP image has loading="lazy" - remove it!');
}
if (!element.decoding) {
console.log('โ ๏ธ Add decoding="async" to image');
}
}
if (element.tagName === 'VIDEO') {
console.log('โข Add preload="metadata" or "auto"');
console.log('โข Provide poster image');
console.log('โข Consider using image placeholder');
}
if (['H1', 'H2', 'H3', 'P', 'DIV'].includes(element.tagName)) {
console.log('โข Ensure font is preloaded');
console.log('โข Use font-display: optional');
console.log('โข Minimize render-blocking CSS');
console.log('โข Inline critical CSS');
}
// Resource hints
console.log('\n๐ Add Resource Hints:');
console.log('```html');
console.log('<!-- Preconnect to required origins -->');
console.log('<link rel="preconnect" href="https://fonts.gstatic.com">');
console.log('<!-- Preload critical resources -->');
console.log('<link rel="preload" href="/hero-image.webp" as="image">');
console.log('<!-- DNS prefetch for third-parties -->');
console.log('<link rel="dns-prefetch" href="https://cdn.example.com">');
console.log('```');
}
}).observe({ entryTypes: ['largest-contentful-paint'] });
}
optimizeLCP();
2. CLS (Cumulative Layout Shift) Prevention
// CLS prevention analyzer
function preventCLS() {
console.log('\n๐ CLS Prevention Analysis\n');
let shifts = [];
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
shifts.push({
value: entry.value,
sources: entry.sources?.map(source => ({
node: source.node,
previousRect: source.previousRect,
currentRect: source.currentRect
}))
});
}
}
}).observe({ entryTypes: ['layout-shift'] });
// Analyze common CLS causes
setTimeout(() => {
console.log(`Total shifts detected: ${shifts.length}`);
console.log(`Cumulative score: ${shifts.reduce((acc, s) => acc + s.value, 0).toFixed(3)}`);
console.log('\n๐ Common CLS Causes & Solutions:');
// Check for images without dimensions
const images = document.querySelectorAll('img');
const imagesWithoutSize = Array.from(images).filter(img =>
!img.hasAttribute('width') || !img.hasAttribute('height')
);
if (imagesWithoutSize.length > 0) {
console.log(`\nโ ${imagesWithoutSize.length} images without dimensions:`);
imagesWithoutSize.slice(0, 3).forEach(img => {
console.log(` - ${img.src.split('/').pop()}`);
});
console.log(' Fix: Add width and height attributes');
}
// Check for web fonts
const fontFaces = document.fonts;
if (fontFaces.size > 0) {
console.log('\nโ ๏ธ Web fonts detected:');
console.log(' Fix: Use font-display: optional or swap');
console.log(' Fix: Preload critical fonts');
}
// Check for dynamic content
const asyncScripts = document.querySelectorAll('script[async], script[defer]');
if (asyncScripts.length > 0) {
console.log(`\nโ ๏ธ ${asyncScripts.length} async/defer scripts that may inject content`);
console.log(' Fix: Reserve space for dynamic content');
console.log(' Fix: Use skeleton screens');
}
// Provide CSS solutions
console.log('\n๐ก CSS Solutions for CLS:');
console.log('```css');
console.log('/* Reserve space for images */');
console.log('img { aspect-ratio: 16 / 9; }');
console.log('\n/* Prevent font swap layout shift */');
console.log('@font-face {');
console.log(' font-family: "MyFont";');
console.log(' src: url("font.woff2");');
console.log(' font-display: optional;');
console.log('}');
console.log('\n/* Reserve space for ads/embeds */');
console.log('.ad-container {');
console.log(' min-height: 250px;');
console.log(' contain: layout;');
console.log('}');
console.log('```');
}, 2000);
}
preventCLS();
3. FID (First Input Delay) Optimization
// FID optimization analyzer
function optimizeFID() {
console.log('\nโก FID Optimization Analysis\n');
// Analyze long tasks
const longTasks = [];
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
longTasks.push({
duration: entry.duration,
startTime: entry.startTime,
attribution: entry.attribution
});
}
}).observe({ entryTypes: ['longtask'] });
// JavaScript execution analysis
setTimeout(() => {
console.log('๐ JavaScript Execution Analysis:');
// Check script count and size
const scripts = document.querySelectorAll('script');
const externalScripts = Array.from(scripts).filter(s => s.src);
const inlineScripts = Array.from(scripts).filter(s => !s.src && s.textContent);
console.log(`\nScripts found:`);
console.log(` External: ${externalScripts.length}`);
console.log(` Inline: ${inlineScripts.length}`);
if (longTasks.length > 0) {
console.log(`\nโ Long tasks detected: ${longTasks.length}`);
longTasks.forEach((task, i) => {
console.log(` Task ${i + 1}: ${task.duration.toFixed(0)}ms`);
});
}
// Optimization strategies
console.log('\n๐ก FID Optimization Strategies:');
console.log('1. Code splitting:');
console.log(' โข Split bundles by route');
console.log(' โข Lazy load non-critical code');
console.log(' โข Use dynamic imports');
console.log('\n2. Optimize JavaScript execution:');
console.log(' โข Defer non-critical scripts');
console.log(' โข Use Web Workers for heavy computation');
console.log(' โข Implement request idle callback');
console.log('\n3. Reduce main thread work:');
console.log(' โข Minimize polyfills');
console.log(' โข Tree-shake unused code');
console.log(' โข Optimize third-party scripts');
// Code examples
console.log('\n๐ Implementation Examples:');
console.log('```javascript');
console.log('// Use dynamic imports');
console.log('button.addEventListener("click", async () => {');
console.log(' const module = await import("./heavy-feature.js");');
console.log(' module.init();');
console.log('});');
console.log('\n// Defer execution to idle time');
console.log('requestIdleCallback(() => {');
console.log(' analytics.track("pageview");');
console.log('});');
console.log('\n// Break up long tasks');
console.log('async function processLargeArray(items) {');
console.log(' for (let i = 0; i < items.length; i += 100) {');
console.log(' await new Promise(resolve => setTimeout(resolve, 0));');
console.log(' processBatch(items.slice(i, i + 100));');
console.log(' }');
console.log('}');
console.log('```');
}, 1000);
}
optimizeFID();
Real-Time Performance Monitoring
1. Performance Budget Monitor
// Monitor performance budget in real-time
class PerformanceBudgetMonitor {
constructor(budgets) {
this.budgets = budgets;
this.violations = [];
this.startMonitoring();
}
startMonitoring() {
console.log('\n๐ Performance Budget Monitor Active\n');
// Monitor Core Web Vitals
this.monitorVitals();
// Monitor resource usage
this.monitorResources();
// Report violations
setInterval(() => this.reportViolations(), 5000);
}
monitorVitals() {
// LCP monitoring
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
const lcpTime = lcp.renderTime || lcp.loadTime;
if (lcpTime > this.budgets.LCP) {
this.addViolation('LCP', lcpTime, this.budgets.LCP);
}
}).observe({ entryTypes: ['largest-contentful-paint'] });
// CLS monitoring
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
if (clsValue > this.budgets.CLS) {
this.addViolation('CLS', clsValue, this.budgets.CLS);
}
}).observe({ entryTypes: ['layout-shift'] });
}
monitorResources() {
const resources = performance.getEntriesByType('resource');
const totalSize = resources.reduce((acc, r) => acc + (r.transferSize || 0), 0);
const jsSize = resources
.filter(r => r.name.endsWith('.js'))
.reduce((acc, r) => acc + (r.transferSize || 0), 0);
if (totalSize > this.budgets.totalSize) {
this.addViolation('Total Size', totalSize, this.budgets.totalSize);
}
if (jsSize > this.budgets.jsSize) {
this.addViolation('JavaScript Size', jsSize, this.budgets.jsSize);
}
}
addViolation(metric, actual, budget) {
const exists = this.violations.find(v => v.metric === metric);
if (exists) {
exists.actual = actual;
} else {
this.violations.push({ metric, actual, budget });
}
}
reportViolations() {
if (this.violations.length === 0) {
console.log('โ
All performance budgets met');
return;
}
console.log('โ Performance Budget Violations:');
console.log('Metric | Actual | Budget | Over Budget');
console.log('-------|--------|--------|------------');
this.violations.forEach(v => {
const actual = v.metric === 'CLS' ? v.actual.toFixed(3) :
v.metric.includes('Size') ? `${(v.actual / 1024).toFixed(0)}KB` :
`${v.actual.toFixed(0)}ms`;
const budget = v.metric === 'CLS' ? v.budget.toFixed(3) :
v.metric.includes('Size') ? `${(v.budget / 1024).toFixed(0)}KB` :
`${v.budget}ms`;
const overBudget = v.metric === 'CLS' ?
((v.actual - v.budget) * 100).toFixed(0) + '%' :
((v.actual / v.budget - 1) * 100).toFixed(0) + '%';
console.log(`${v.metric.padEnd(7)} | ${actual.padEnd(8)} | ${budget.padEnd(8)} | +${overBudget}`);
});
}
}
// Initialize with budgets
const budgetMonitor = new PerformanceBudgetMonitor({
LCP: 2500, // 2.5 seconds
CLS: 0.1, // 0.1 score
FID: 100, // 100ms
totalSize: 1048576, // 1MB
jsSize: 300000 // 300KB
});
2. Real User Monitoring (RUM)
// Simplified RUM implementation
class RealUserMonitoring {
constructor() {
this.metrics = [];
this.sessionId = this.generateSessionId();
this.collectMetrics();
}
generateSessionId() {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
collectMetrics() {
console.log('\n๐ก Real User Monitoring Active\n');
// Collect page load metrics
window.addEventListener('load', () => {
const navigation = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByType('paint');
const metrics = {
sessionId: this.sessionId,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
connection: navigator.connection?.effectiveType || 'unknown',
// Core metrics
TTFB: navigation.responseStart - navigation.requestStart,
FCP: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0,
DCL: navigation.domContentLoadedEventEnd - navigation.fetchStart,
Load: navigation.loadEventEnd - navigation.fetchStart,
// Additional context
resources: performance.getEntriesByType('resource').length,
transferSize: performance.getEntriesByType('resource')
.reduce((acc, r) => acc + (r.transferSize || 0), 0),
cacheHitRate: this.calculateCacheHitRate()
};
this.metrics.push(metrics);
this.reportMetrics();
});
// Collect Web Vitals
this.collectWebVitals();
// Monitor errors
this.monitorErrors();
}
collectWebVitals() {
// LCP
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
this.metrics.push({
metric: 'LCP',
value: lcp.renderTime || lcp.loadTime,
timestamp: Date.now()
});
}).observe({ entryTypes: ['largest-contentful-paint'] });
// CLS
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
}).observe({ entryTypes: ['layout-shift'] });
// Report CLS on page unload
window.addEventListener('beforeunload', () => {
this.metrics.push({
metric: 'CLS',
value: clsValue,
timestamp: Date.now()
});
});
}
calculateCacheHitRate() {
const resources = performance.getEntriesByType('resource');
const cached = resources.filter(r => r.transferSize === 0 && r.decodedBodySize > 0);
return resources.length > 0 ? (cached.length / resources.length * 100).toFixed(1) : 0;
}
monitorErrors() {
window.addEventListener('error', (event) => {
this.metrics.push({
type: 'error',
message: event.message,
source: event.filename,
line: event.lineno,
timestamp: Date.now()
});
});
}
reportMetrics() {
console.log('๐ RUM Metrics Collected:');
console.log(`Session ID: ${this.sessionId}`);
console.log(`Metrics collected: ${this.metrics.length}`);
// In production, send to analytics endpoint
console.log('\n๐ค Would send to analytics:', {
endpoint: 'https://analytics.example.com/rum',
method: 'POST',
data: this.metrics
});
}
}
// Initialize RUM
const rum = new RealUserMonitoring();
Complete Performance Audit
Comprehensive Performance Assessment
// Run complete performance audit
async function completePerformanceAudit() {
console.log('โก COMPLETE PERFORMANCE AUDIT');
console.log('โ'.repeat(50));
console.log(`URL: ${window.location.href}`);
console.log(`Date: ${new Date().toLocaleDateString()}\n`);
const audit = {
score: 0,
vitals: {},
issues: [],
opportunities: []
};
// Phase 1: Core Web Vitals
console.log('๐ PHASE 1: CORE WEB VITALS');
console.log('โ'.repeat(40));
// Wait for metrics to be collected
await new Promise(resolve => setTimeout(resolve, 3000));
// Collect vital metrics (simplified for demo)
const navigation = performance.getEntriesByType('navigation')[0];
const paint = performance.getEntriesByType('paint');
audit.vitals = {
FCP: paint.find(p => p.name === 'first-contentful-paint')?.startTime || 0,
TTFB: navigation.responseStart - navigation.requestStart,
DCL: navigation.domContentLoadedEventEnd - navigation.fetchStart,
Load: navigation.loadEventEnd - navigation.fetchStart
};
// Score vitals
Object.entries(audit.vitals).forEach(([metric, value]) => {
const thresholds = {
FCP: { good: 1800, poor: 3000 },
TTFB: { good: 800, poor: 1800 },
DCL: { good: 1500, poor: 3500 },
Load: { good: 3000, poor: 5000 }
};
const rating = value <= thresholds[metric].good ? 'Good' :
value <= thresholds[metric].poor ? 'Needs Improvement' : 'Poor';
const icon = rating === 'Good' ? 'โ
' : rating === 'Needs Improvement' ? '๐ก' : 'โ';
console.log(`${icon} ${metric}: ${value.toFixed(0)}ms - ${rating}`);
if (rating === 'Good') audit.score += 25;
else if (rating === 'Needs Improvement') audit.score += 12;
});
// Phase 2: Resource Analysis
console.log('\n๐ฆ PHASE 2: RESOURCE ANALYSIS');
console.log('โ'.repeat(40));
const resources = performance.getEntriesByType('resource');
const resourceStats = {
total: resources.length,
totalSize: resources.reduce((acc, r) => acc + (r.transferSize || 0), 0),
byType: {}
};
resources.forEach(resource => {
const ext = resource.name.split('.').pop().split('?')[0] || 'other';
if (!resourceStats.byType[ext]) {
resourceStats.byType[ext] = { count: 0, size: 0 };
}
resourceStats.byType[ext].count++;
resourceStats.byType[ext].size += resource.transferSize || 0;
});
console.log(`Total resources: ${resourceStats.total}`);
console.log(`Total size: ${(resourceStats.totalSize / 1024 / 1024).toFixed(2)}MB`);
// Find heavy resources
const heavyResources = resources
.filter(r => r.transferSize > 100000)
.sort((a, b) => b.transferSize - a.transferSize)
.slice(0, 3);
if (heavyResources.length > 0) {
console.log('\nโ ๏ธ Heavy resources:');
heavyResources.forEach(r => {
console.log(` - ${r.name.split('/').pop().substring(0, 40)}: ${(r.transferSize / 1024).toFixed(0)}KB`);
audit.issues.push(`Heavy resource: ${r.name.split('/').pop()}`);
});
}
// Phase 3: Optimization Opportunities
console.log('\n๐ก PHASE 3: OPTIMIZATION OPPORTUNITIES');
console.log('โ'.repeat(40));
// Check for optimization opportunities
const images = document.querySelectorAll('img');
const lazyImages = Array.from(images).filter(img => img.loading === 'lazy').length;
const missingAlt = Array.from(images).filter(img => !img.alt).length;
if (images.length > 0 && lazyImages < images.length * 0.8) {
audit.opportunities.push('Enable lazy loading for off-screen images');
}
if (missingAlt > 0) {
audit.opportunities.push(`Add alt text to ${missingAlt} images`);
}
// Check compression
const textResources = resources.filter(r =>
r.name.includes('.js') || r.name.includes('.css') || r.name.includes('.html')
);
const uncompressed = textResources.filter(r =>
!r.responseHeaders?.includes('gzip') && !r.responseHeaders?.includes('br')
);
if (uncompressed.length > 0) {
audit.opportunities.push(`Enable compression for ${uncompressed.length} text resources`);
}
// Print opportunities
audit.opportunities.forEach(opp => {
console.log(`โข ${opp}`);
});
// Final Score
console.log('\n๐ AUDIT SUMMARY');
console.log('โ'.repeat(50));
console.log(`Performance Score: ${audit.score}/100`);
console.log(`Issues Found: ${audit.issues.length}`);
console.log(`Opportunities: ${audit.opportunities.length}`);
// Recommendations
console.log('\n๐ฏ TOP RECOMMENDATIONS:');
console.log('1. Optimize largest contentful paint element');
console.log('2. Reduce JavaScript execution time');
console.log('3. Eliminate render-blocking resources');
console.log('4. Serve images in next-gen formats');
console.log('5. Implement resource hints (preload, prefetch)');
return audit;
}
// Run the audit
completePerformanceAudit();
The Bottom Line
Performance is user experience measured in milliseconds:
- Core Web Vitals are ranking factors - Google rewards fast sites
- Users expect instant - 3 seconds is too long in 2024
- Every 100ms matters - Direct correlation to revenue
- Mobile performance is critical - Most users on slow connections
- Monitoring must be continuous - Performance degrades over time
Measure everything. Optimize relentlessly. Monitor continuously. Your users' patience depends on it.
Monitor performance instantly: Fusebox tracks Core Web Vitals, resource loading, and performance metrics in real-time while you browse. Never miss a performance regression. $29 one-time purchase.