SalakCode SalakCode
Performance

Core Web Vitals

Essential metrics for web performance

Intermediate
performance metrics seo lighthouse

Definition

Core Web Vitals are a set of three specific factors that Google considers important in a webpage’s overall user experience. They measure loading performance, interactivity, and visual stability, and are used as ranking signals for search results.

The Three Metrics

1. Largest Contentful Paint (LCP)

// Measures loading performance
// Target: ≤ 2.5 seconds

// Measuring LCP with Performance Observer
new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1];
  console.log('LCP:', lastEntry.startTime);
}).observe({ entryTypes: ['largest-contentful-paint'] });

// Common LCP elements:
// - Hero image
// - Video poster
// - Large text block
// - Background image

Optimizing LCP:

// 1. Preload critical resources
<link rel="preload" as="image" href="/hero.jpg">

// 2. Optimize images
// Use WebP, AVIF formats
// Implement responsive images
<picture>
  <source srcset="hero.avif" type="image/avif">
  <img src="hero.jpg" alt="Hero" width="1200" height="600">
</picture>

// 3. Reduce server response time
// Use CDN
// Implement caching
// Optimize database queries

// 4. Remove render-blocking resources
// Inline critical CSS
// Defer non-critical JavaScript
<script src="analytics.js" defer></script>

2. First Input Delay (FID) → Interaction to Next Paint (INP)

// Measures interactivity
// FID Target: ≤ 100ms
// INP Target: ≤ 200ms (new metric replacing FID)

// Measuring FID
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    const delay = entry.processingStart - entry.startTime;
    console.log('FID:', delay);
  }
}).observe({ entryTypes: ['first-input'] });

// Measuring INP (Interaction to Next Paint)
let inpValue = 0;
let interactions = [];

new PerformanceObserver((list) => {
  const entries = list.getEntries();
  for (const entry of entries) {
    // Track all interactions
    interactions.push(entry);

    // Keep only the last 10 seconds of interactions
    const cutoff = entry.startTime - 10000;
    interactions = interactions.filter(e => e.startTime >= cutoff);

    // INP is the worst interaction latency (or 98th percentile for many interactions)
    if (interactions.length > 0) {
      const latencies = interactions.map(e => e.duration);
      inpValue = Math.max(...latencies);
      console.log('INP:', inpValue);
    }
  }
}).observe({
  type: 'event',
  buffered: true,
  durationThreshold: 16 // Only report interactions > 16ms
});

Optimizing FID/INP:

// 1. Break up Long Tasks
function processLargeArray(items) {
  const chunkSize = 50;
  let index = 0;
  
  function processChunk() {
    const chunk = items.slice(index, index + chunkSize);
    processItems(chunk);
    
    index += chunkSize;
    if (index < items.length) {
      requestIdleCallback(processChunk);
    }
  }
  
  processChunk();
}

// 2. Use Web Workers for heavy computation
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataset });

// 3. Reduce JavaScript execution time
// Code splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));

// Tree shaking
// Remove unused code

// 4. Optimize event handlers
// Debounce scroll events
const debouncedScroll = debounce(handleScroll, 16);
window.addEventListener('scroll', debouncedScroll);

3. Cumulative Layout Shift (CLS)

// Measures visual stability
// Target: ≤ 0.1

// Measuring CLS
let clsValue = 0;

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (!entry.hadRecentInput) {
      clsValue += entry.value;
      console.log('Current CLS:', clsValue);
    }
  }
}).observe({ entryTypes: ['layout-shift'] });

Optimizing CLS:

<!-- 1. Always include size attributes on images -->
<img src="photo.jpg" width="800" height="600" alt="Photo">

<!-- 2. Reserve space for dynamic content -->
<div style="min-height: 200px;">
  <!-- Content loads here -->
</div>

<!-- 3. Avoid inserting content above existing content -->
<!-- Bad: Banner pushes content down -->
<div id="banner">New announcement!</div>

<!-- Good: Reserve space for banner -->
<div style="height: 50px;">
  <div id="banner">New announcement!</div>
</div>

<!-- 4. Use font-display: optional or swap -->
<style>
  @font-face {
    font-family: 'Custom Font';
    src: url('font.woff2') format('woff2');
    font-display: swap; /* or optional */
  }
</style>

Monitoring Tools

Chrome DevTools

// Performance panel
// - Record and analyze
// - View LCP, FID, CLS in real-time

// Lighthouse panel
// - Run audits
// - Get optimization suggestions

Web Vitals Library

import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getLCP(console.log);
getFCP(console.log); // First Contentful Paint
getTTFB(console.log); // Time to First Byte

// Send to analytics
function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  
  // Use navigator.sendBeacon for reliable delivery
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/analytics', body);
  } else {
    fetch('/analytics', { body, method: 'POST', keepalive: true });
  }
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);

Real User Monitoring (RUM)

// Track Core Web Vitals for actual users
import { getCLS, getFID, getLCP } from 'web-vitals';

function reportWebVitals({ name, delta, id }) {
  // Send to Google Analytics
  gtag('event', name, {
    event_category: 'Web Vitals',
    value: Math.round(name === 'CLS' ? delta * 1000 : delta),
    event_label: id,
    non_interaction: true,
  });
}

getCLS(reportWebVitals);
getFID(reportWebVitals);
getLCP(reportWebVitals);

Performance Budgets

// Set budgets in your build tool
// webpack.config.js
module.exports = {
  performance: {
    maxEntrypointSize: 250000, // 250kb
    maxAssetSize: 250000,
    hints: 'error'
  }
};

// Use Lighthouse CI for automated testing
// .github/workflows/ci.yml
- name: Audit URLs using Lighthouse
  uses: treosh/lighthouse-ci-action@v9
  with:
    urls: |
      https://example.com/
    budgetPath: ./budget.json
Key Takeaway

Core Web Vitals are essential for user experience and SEO. Focus on LCP (loading), FID/INP (interactivity), and CLS (visual stability). Use the web-vitals library to measure in production, optimize critical rendering paths, and set performance budgets to maintain fast experiences.

Resources

Related Topics