Back to blog
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.