Performance

Core Web Vitals Optimization

Step-by-step guide to optimizing Core Web Vitals. Improve your website's LCP, FID, and CLS for better rankings and user experience.

René Lauenroth
15 min read

Core Web Vitals have been an official Google ranking factor since 2021. In this guide, I’ll show you how to systematically optimize the three core metrics – with concrete actions and code examples.

What Are Core Web Vitals?

Google measures user experience with Core Web Vitals using three metrics:

MetricDescriptionGoodNeeds ImprovementPoor
LCPLargest Contentful Paint≤ 2.5s≤ 4.0s> 4.0s
FID/INPFirst Input Delay / Interaction to Next Paint≤ 100ms≤ 300ms> 300ms
CLSCumulative Layout Shift≤ 0.1≤ 0.25> 0.25

1. Optimizing LCP (Largest Contentful Paint)

LCP measures how long it takes until the largest visible element is loaded.

Common LCP Problems

  1. Slow server response
  2. Render-blocking resources
  3. Non-optimized images
  4. Client-Side Rendering

Solution: Optimize Server Response

Improve TTFB (Time to First Byte):

# Nginx configuration
gzip on;
gzip_types text/plain text/css application/json application/javascript;

# Browser caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|webp)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

PHP Optimizations:

  • Enable OPcache
  • Optimize database queries
  • Use caching layer (Redis, Memcached)

Solution: Optimize Images

Use modern image 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">
</picture>

Prioritize LCP image:

<!-- Load hero image with high priority -->
<link rel="preload" as="image" href="/hero.webp" type="image/webp">

<img
  src="/hero.webp"
  alt="Hero"
  fetchpriority="high"
  loading="eager"
>

Solution: Inline Critical CSS

<head>
  <style>
    /* Critical CSS for above-the-fold */
    body { margin: 0; font-family: sans-serif; }
    .hero { height: 100vh; display: flex; align-items: center; }
    .hero h1 { font-size: 3rem; }
  </style>

  <!-- Load remaining CSS asynchronously -->
  <link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>

2. Optimizing FID/INP (Interactivity)

FID measures the time from the first user interaction to the browser’s response. INP (Interaction to Next Paint) is replacing FID as the new metric and measures all interactions.

Common Interactivity Problems

  1. Long JavaScript execution
  2. Blocking third-party scripts
  3. Unnecessary JavaScript bundles
  4. Hydration in SSR frameworks

Solution: Split JavaScript

Code-Splitting with dynamic imports:

// Instead of loading everything immediately
import { heavyFunction } from './heavy-module.js';

// Load only when needed
const button = document.querySelector('#action-button');
button.addEventListener('click', async () => {
  const { heavyFunction } = await import('./heavy-module.js');
  heavyFunction();
});

Solution: Avoid Long Tasks

Split tasks into smaller units:

// Bad: One long task
function processItems(items) {
  items.forEach(item => processItem(item)); // Blocks the main thread
}

// Better: With yielding
async function processItemsWithYielding(items) {
  for (const item of items) {
    processItem(item);

    // Give the browser time for other tasks
    if (shouldYield()) {
      await scheduler.yield();
    }
  }
}

function shouldYield() {
  return performance.now() - taskStartTime > 50; // 50ms chunks
}

Solution: Optimize Third-Party Scripts

<!-- Defer for non-critical scripts -->
<script defer src="https://analytics.example.com/script.js"></script>

<!-- Or dynamic loading after user interaction -->
<script>
  let analyticsLoaded = false;

  function loadAnalytics() {
    if (analyticsLoaded) return;
    analyticsLoaded = true;

    const script = document.createElement('script');
    script.src = 'https://analytics.example.com/script.js';
    document.body.appendChild(script);
  }

  // Load after first interaction or after 5 seconds
  ['scroll', 'click', 'touchstart'].forEach(event => {
    window.addEventListener(event, loadAnalytics, { once: true });
  });
  setTimeout(loadAnalytics, 5000);
</script>

3. Optimizing CLS (Layout Stability)

CLS measures how much elements shift during loading.

Common CLS Problems

  1. Images without dimensions
  2. Dynamically inserted content
  3. Web fonts (FOIT/FOUT)
  4. Ads and embeds

Solution: Specify Dimensions for Media

<!-- Always specify width and height -->
<img
  src="/image.jpg"
  alt="Description"
  width="800"
  height="600"
>

<!-- Or with aspect-ratio in CSS -->
<style>
  .responsive-image {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
  }
</style>

Solution: Placeholders for Dynamic Content

/* Fixed height for ad banners */
.ad-container {
  min-height: 250px;
  background: #f0f0f0;
}

/* Skeleton screens for loading times */
.skeleton {
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

Solution: Optimize Web Fonts

/* font-display: swap prevents FOIT */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap;
}

/* Optional: Fallback with similar metrics */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%; /* Adjustment to system font */
}

Font preloading:

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

Measurement and Monitoring

Tools for Core Web Vitals

  1. PageSpeed Insights – Quick test with recommendations
  2. Google Search Console – Real user data (CrUX)
  3. Chrome DevTools – Performance tab, Lighthouse
  4. Web Vitals Extension – Real-time measurement while browsing

Real User Monitoring (RUM)

// Include Web Vitals library
import { onCLS, onFID, onLCP, onINP } from 'web-vitals';

function sendToAnalytics({ name, value, rating }) {
  // Send to your analytics system
  gtag('event', name, {
    value: Math.round(name === 'CLS' ? value * 1000 : value),
    event_category: 'Web Vitals',
    event_label: rating,
    non_interaction: true,
  });
}

onCLS(sendToAnalytics);
onFID(sendToAnalytics);
onLCP(sendToAnalytics);
onINP(sendToAnalytics);

Prioritization: What to Optimize First?

Order by impact:

  1. LCP – Greatest influence on perceived speed
  2. CLS – Frustrates users the most
  3. FID/INP – Important for interactive pages

Identify quick wins:

ProblemEffortImpact
Optimize imagesLowHigh
Lazy loadingLowMedium
Font optimizationLowMedium
Code splittingMediumHigh
Server-side renderingHighVery high

Conclusion

Optimizing Core Web Vitals isn’t a one-time project but a continuous process. Start with quick wins and work your way to more complex optimizations.

Key takeaways:

  1. Measure regularly – both lab and field data
  2. Prioritize by impact and effort
  3. Test changes on staging environments
  4. Monitor effects after deployment

Need help with performance optimization? I analyze your website and develop a customized optimization plan.

Tags:

#Performance #Core Web Vitals #SEO #LCP #CLS #PageSpeed

René Lauenroth

Web developer with over 30 years of experience. Specialized in TYPO3, WordPress and AI integration.

Learn more

Related Articles

Have questions about this topic?

I'm happy to advise you on your web project.

Get in touch