← Back to Guides

Web Performance Optimization: Core Web Vitals

📖 16 min read | 📅 Updated: January 2025 | 🏷️ Web Development

Understanding Core Web Vitals

Core Web Vitals are Google's metrics for measuring user experience. The three key metrics are LCP (Largest Contentful Paint), FID (First Input Delay), and CLS (Cumulative Layout Shift). Optimizing these metrics improves SEO rankings and user satisfaction.

1. Largest Contentful Paint (LCP)

LCP measures loading performance. It should occur within 2.5 seconds of page load.

Optimize Images

<!-- Use modern formats -->
<picture>
  <source srcset="image.avif" type="image/avif">
  <source srcset="image.webp" type="image/webp">
  <img src="image.jpg" alt="Description" 
       width="800" height="600"
       loading="lazy">
</picture>

<!-- Responsive images -->
<img srcset="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"
     sizes="(max-width: 600px) 480px, (max-width: 900px) 800px, 1200px"
     src="large.jpg" alt="Responsive image">

Preload Critical Resources

<head>
  <!-- Preload LCP image -->
  <link rel="preload" as="image" href="hero.jpg">
  
  <!-- Preload fonts -->
  <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
  
  <!-- Preconnect to external domains -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="dns-prefetch" href="https://analytics.google.com">
</head>

Optimize Server Response

// Enable compression
const compression = require('compression');
app.use(compression());

// Set proper cache headers
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');

// Use CDN for static assets
const cdnUrl = 'https://cdn.example.com';

// Implement HTTP/2 Server Push
res.push('/styles/critical.css');

2. First Input Delay (FID)

FID measures interactivity. It should be less than 100ms.

Minimize JavaScript

// Code splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));

// Defer non-critical scripts
<script src="analytics.js" defer></script>

// Use Web Workers for heavy tasks
const worker = new Worker('heavy-calc.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => {
  console.log('Result:', e.data);
};

Break Up Long Tasks

// Bad: Long blocking task
function processLargeArray(arr) {
  arr.forEach(item => heavyOperation(item));
}

// Good: Break into chunks
async function processLargeArray(arr) {
  const chunkSize = 100;
  for (let i = 0; i < arr.length; i += chunkSize) {
    const chunk = arr.slice(i, i + chunkSize);
    await new Promise(resolve => {
      requestIdleCallback(() => {
        chunk.forEach(item => heavyOperation(item));
        resolve();
      });
    });
  }
}

3. Cumulative Layout Shift (CLS)

CLS measures visual stability. It should be less than 0.1.

Set Image Dimensions

<!-- Always specify width and height -->
<img src="photo.jpg" width="800" height="600" alt="Photo">

<!-- Use aspect ratio in CSS -->
<style>
  .image-container {
    aspect-ratio: 16 / 9;
    width: 100%;
  }
</style>

Reserve Space for Ads

.ad-container {
  min-height: 250px;
  background: #f0f0f0;
}

.ad-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 250px;
}

Avoid Inserting Content Above Existing Content

// Bad: Inserts banner at top, pushing content down
document.body.prepend(banner);

// Good: Reserve space or insert at bottom
const placeholder = document.getElementById('banner-placeholder');
placeholder.replaceWith(banner);

4. Resource Optimization

Minify and Bundle

// webpack.config.js
module.exports = {
  mode: 'production',
  optimization: {
    minimize: true,
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10
        }
      }
    }
  }
};

Lazy Load Images

<img src="image.jpg" loading="lazy" alt="Lazy loaded">

<!-- Intersection Observer for custom lazy loading -->
<script>
const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      imageObserver.unobserve(img);
    }
  });
});

document.querySelectorAll('img[data-src]').forEach(img => {
  imageObserver.observe(img);
});
</script>

5. Critical CSS

<head>
  <style>
    /* Inline critical CSS */
    body { margin: 0; font-family: system-ui; }
    .hero { min-height: 100vh; }
  </style>
  
  <!-- Load full CSS asynchronously -->
  <link rel="preload" href="/styles/main.css" as="style" 
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/styles/main.css"></noscript>
</head>

6. Font Optimization

/* Use font-display */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap; /* or optional, fallback */
  font-weight: 400;
}

/* Subset fonts */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

/* Preload fonts */
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>

7. Monitoring Performance

// Web Vitals library
import {getCLS, getFID, getLCP} from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getLCP(console.log);

// Performance Observer API
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('LCP:', entry.renderTime || entry.loadTime);
  }
});
observer.observe({type: 'largest-contentful-paint', buffered: true});

// Send to analytics
function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  fetch('/analytics', {
    method: 'POST',
    body,
    headers: {'Content-Type': 'application/json'}
  });
}

8. Caching Strategies

// Service Worker caching
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('v1').then((cache) => {
      return cache.addAll([
        '/',
        '/styles/main.css',
        '/scripts/app.js',
        '/images/logo.png'
      ]);
    })
  );
});

// HTTP caching headers
Cache-Control: public, max-age=31536000, immutable  // Static assets
Cache-Control: public, max-age=3600, must-revalidate  // HTML
Cache-Control: private, no-cache  // User-specific data
💡 Performance Checklist:

Conclusion

Optimizing Core Web Vitals is essential for modern web development. By focusing on LCP, FID, and CLS, you'll create faster, more responsive websites that rank better in search engines and provide excellent user experiences. Regular monitoring and continuous optimization are key to maintaining peak performance.