Core Web Vitals 14 min read

How to Fix Core Web Vitals Issues: Complete Guide (2025)

Learn how to fix Core Web Vitals issues affecting your site's SEO. Step-by-step fixes for LCP, INP, and CLS problems with real code examples.

By Rankture Team
How to Fix Core Web Vitals Issues: Complete Guide (2025)

If Google Search Console is showing Core Web Vitals issues on your site, you’re not alone. These performance metrics directly impact your rankings, and fixing them can feel overwhelming—especially when you’re not sure where to start.

This guide breaks down exactly how to fix Core Web Vitals issues for each of the three metrics: LCP, INP, and CLS. I’ll give you specific code examples, prioritized fixes, and the exact steps to take your scores from red to green.

Understanding Your Core Web Vitals Issues

Before diving into fixes, let’s understand what you’re dealing with:

MetricMeasuresGoodNeeds WorkPoor
LCPLoading speed≤2.5s2.5-4s>4s
INPInteractivity≤200ms200-500ms>500ms
CLSVisual stability≤0.10.1-0.25>0.25

The goal is to get 75% of page views into the “Good” threshold for each metric.


How to Fix LCP (Largest Contentful Paint) Issues

LCP measures how quickly your main content loads. If you’re failing LCP, here’s the systematic approach to fix it.

Step 1: Identify Your LCP Element

First, find out what element is being measured as your LCP:

  1. Open Chrome DevTools (F12)
  2. Go to Performance tab
  3. Click the refresh button to record a page load
  4. Find the “LCP” marker in the timeline
  5. Click it to see which element is the LCP

Common LCP elements:

Step 2: Optimize Images (Most Common Fix)

If your LCP element is an image, these optimizations usually solve the problem:

Convert to modern formats:

<!-- Before: Large JPG -->
<img src="hero.jpg" alt="Hero">

<!-- After: WebP with fallback -->
<picture>
  <source srcset="hero.webp" type="image/webp">
  <source srcset="hero.jpg" type="image/jpeg">
  <img src="hero.jpg" alt="Hero" width="1200" height="600">
</picture>

Preload the LCP image:

<head>
  <link rel="preload" as="image" href="/hero.webp" type="image/webp">
</head>

Use fetchpriority:

<img src="hero.webp" fetchpriority="high" alt="Hero" width="1200" height="600">

Serve responsive images:

<img 
  src="hero-800.webp"
  srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
  sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
  alt="Hero"
  width="1200"
  height="600"
  fetchpriority="high"
>

Step 3: Reduce Server Response Time

If your Time to First Byte (TTFB) is over 600ms, no amount of image optimization will fix LCP.

Quick fixes:

  1. Use a CDN - Serve assets from edge locations near users
  2. Enable compression - Gzip or Brotli compression
  3. Upgrade hosting - Move from shared to VPS/dedicated hosting
  4. Enable caching - Browser caching and server-side caching

Check your TTFB:

// Run in browser console
const timing = performance.timing;
const ttfb = timing.responseStart - timing.requestStart;
console.log(`TTFB: ${ttfb}ms`);

Step 4: Eliminate Render-Blocking Resources

CSS and JavaScript that block rendering delay your LCP.

Move critical CSS inline:

<head>
  <style>
    /* Only above-the-fold critical styles */
    .hero { background: #f0f0f0; min-height: 400px; }
    .nav { display: flex; justify-content: space-between; }
  </style>
  <!-- Load full CSS asynchronously -->
  <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>

Defer non-critical JavaScript:

<!-- Before: Blocking -->
<script src="analytics.js"></script>

<!-- After: Non-blocking -->
<script src="analytics.js" defer></script>

<!-- Or for third-party scripts -->
<script src="widget.js" async></script>

Step 5: Fix Client-Side Rendering Issues

If you’re using React, Vue, or Angular with client-side rendering, your LCP might be delayed while JavaScript builds the page.

Solutions:

  1. Server-side rendering (SSR) - Render HTML on the server
  2. Static site generation (SSG) - Pre-render pages at build time
  3. Hybrid approach - SSR/SSG for critical content, hydrate for interactivity

How to Fix INP (Interaction to Next Paint) Issues

INP measures how quickly your page responds to user interactions. This is often the hardest metric to fix because it requires JavaScript optimization.

Step 1: Identify Slow Interactions

Use Chrome DevTools to find what’s causing INP issues:

  1. Open DevTools → Performance tab
  2. Enable “Web Vitals” in settings
  3. Click record
  4. Interact with the page (click buttons, submit forms)
  5. Stop recording
  6. Look for “Long Tasks” (>50ms) highlighted in red

Step 2: Break Up Long Tasks

JavaScript tasks over 50ms block the main thread and hurt INP.

Before (blocking):

function processLargeArray(items) {
  items.forEach(item => {
    // Heavy processing
    complexOperation(item);
  });
}

After (yielding to main thread):

async function processLargeArray(items) {
  for (const item of items) {
    complexOperation(item);
    // Yield to main thread every 50ms
    await scheduler.yield();
  }
}

// Fallback if scheduler.yield() not available
function yieldToMain() {
  return new Promise(resolve => setTimeout(resolve, 0));
}

Step 3: Defer Non-Critical JavaScript

Move non-essential scripts out of the critical path:

<!-- Load after page is interactive -->
<script>
  // Wait for user to start interacting
  let interactionStarted = false;
  ['click', 'scroll', 'keydown'].forEach(event => {
    window.addEventListener(event, () => {
      if (!interactionStarted) {
        interactionStarted = true;
        loadNonCriticalScripts();
      }
    }, { once: true });
  });

  function loadNonCriticalScripts() {
    const script = document.createElement('script');
    script.src = 'analytics.js';
    document.body.appendChild(script);
  }
</script>

Step 4: Optimize Event Handlers

Heavy event handlers directly cause INP issues:

Before:

searchInput.addEventListener('input', (e) => {
  const results = searchEntireDatabase(e.target.value);
  renderResults(results);
});

After (debounced):

function debounce(fn, delay) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

searchInput.addEventListener('input', debounce((e) => {
  const results = searchEntireDatabase(e.target.value);
  renderResults(results);
}, 150));

Step 5: Reduce DOM Size

Large DOM trees slow down every interaction:

Target:

How to reduce DOM:

  1. Virtualize long lists (render only visible items)
  2. Remove hidden elements instead of display: none
  3. Lazy-load below-the-fold content
  4. Use CSS instead of DOM elements for decorative elements

Step 6: Use Web Workers for Heavy Computation

Move CPU-intensive work off the main thread:

// main.js
const worker = new Worker('worker.js');

worker.postMessage({ data: largeDataSet });

worker.onmessage = (e) => {
  updateUI(e.data.result);
};

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage({ result });
};

How to Fix CLS (Cumulative Layout Shift) Issues

CLS measures visual stability. Fixing CLS is often the quickest win because the solutions are straightforward.

Step 1: Find What’s Shifting

Use the Layout Shift debugger:

// Paste in browser console
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (entry.hadRecentInput) continue; // Ignore user-initiated shifts
    console.log('Layout Shift:', entry.value, entry.sources);
  }
}).observe({ type: 'layout-shift', buffered: true });

Or use Chrome DevTools:

  1. Open DevTools
  2. Press Cmd/Ctrl + Shift + P
  3. Type “Show Core Web Vitals overlay”
  4. Enable it
  5. Reload the page
  6. Watch for red boxes indicating layout shifts

Step 2: Add Dimensions to Images

The #1 cause of CLS is images without explicit sizes:

Before:

<img src="photo.jpg" alt="Photo">

After:

<img src="photo.jpg" alt="Photo" width="800" height="600">

Or use CSS aspect-ratio:

.image-container {
  aspect-ratio: 16 / 9;
  width: 100%;
}

.image-container img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Step 3: Reserve Space for Dynamic Content

Ads, embeds, and lazy-loaded content need reserved space:

For ads:

<div style="min-height: 250px; background: #f0f0f0;">
  <!-- Ad will load here -->
  <div id="ad-slot-1"></div>
</div>

For embeds:

<div style="aspect-ratio: 16/9; width: 100%;">
  <iframe 
    src="https://www.youtube.com/embed/video-id" 
    style="width: 100%; height: 100%;"
    loading="lazy"
  ></iframe>
</div>

Step 4: Prevent Font Layout Shifts

Web fonts can cause text to reflow when they load:

Use font-display:

@font-face {
  font-family: 'CustomFont';
  src: url('custom-font.woff2') format('woff2');
  font-display: optional; /* No layout shift, falls back gracefully */
}

Preload critical fonts:

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

Match fallback font metrics:

body {
  font-family: 'CustomFont', Arial, sans-serif;
  /* Adjust fallback to match custom font metrics */
  --font-size-adjust: 0.92;
}

Step 5: Don’t Insert Content Above Existing Content

This is a UX issue as much as a technical one:

Before (causes CLS):

// Inserting banner at top of page
document.body.insertBefore(banner, document.body.firstChild);

After (no CLS):

// Reserve space in HTML
<div id="banner-slot" style="min-height: 60px;"></div>

// Then fill it
document.getElementById('banner-slot').appendChild(banner);

Step 6: Use Transform for Animations

Layout properties trigger shifts; transforms don’t:

Before (causes CLS):

.element {
  animation: slide 0.3s;
}

@keyframes slide {
  from { margin-left: -100px; }
  to { margin-left: 0; }
}

After (no CLS):

.element {
  animation: slide 0.3s;
}

@keyframes slide {
  from { transform: translateX(-100px); }
  to { transform: translateX(0); }
}

Prioritizing Core Web Vitals Fixes

Not sure where to start? Here’s the priority order:

High Impact (Fix First)

  1. LCP image optimization - Often the single biggest improvement
  2. CLS dimensions - Quick win with immediate results
  3. Render-blocking resources - Affects all metrics

Medium Impact

  1. Server response time - Requires infrastructure changes
  2. Long tasks - Requires JavaScript refactoring
  3. Font optimization - Prevents flash of unstyled text

Lower Impact (But Still Important)

  1. DOM size reduction - Improves overall performance
  2. Third-party script management - Ongoing maintenance
  3. Animation optimization - Edge cases

Verifying Your Fixes

After implementing fixes, verify they worked:

Quick Check (Lab Data)

  1. Run PageSpeed Insights on your URL
  2. Compare scores before/after
  3. Lab data updates immediately

Authoritative Check (Field Data)

  1. Wait 28 days for Search Console data to update
  2. Check Core Web Vitals report for improvement
  3. Field data is what actually affects rankings

Continuous Monitoring

  1. Set up Search Console alerts for CWV issues
  2. Run periodic audits with Rankture
  3. Test after every major deployment

Common Mistakes When Fixing Core Web Vitals

1. Only Testing the Homepage

Check your most important pages:

2. Ignoring Mobile

Google uses mobile Core Web Vitals for mobile rankings. Always test mobile first—it’s usually worse than desktop.

3. Making Too Many Changes at Once

Change one thing, measure, repeat. Otherwise, you won’t know what actually helped (or hurt).

4. Ignoring Third-Party Scripts

Chat widgets, analytics, and marketing scripts often cause the biggest issues. Audit and defer them aggressively.

5. Not Monitoring After Fixes

Core Web Vitals can regress with new features, content, or third-party updates. Set up ongoing monitoring.


FAQ: Fixing Core Web Vitals

How long until I see ranking improvements?

Google’s data uses a 28-day rolling average. Expect:

Should I focus on mobile or desktop Core Web Vitals?

Mobile first. Google uses mobile-first indexing, and mobile performance is usually worse than desktop.

What if I can’t fix Core Web Vitals without breaking features?

Prioritize by traffic impact:

  1. Core features on high-traffic pages = must fix
  2. Non-essential features = consider removing or deferring
  3. Low-traffic pages = lower priority

Can I pass Core Web Vitals with WordPress/Shopify/etc.?

Yes, but platform-specific approaches help. Check our guides:


Start Fixing Your Core Web Vitals

Core Web Vitals issues are fixable—it just takes systematic effort. Start with the highest-impact fixes (image optimization, explicit dimensions), then work your way through the JavaScript and advanced optimizations.

Not sure where your site stands? Run a free SEO audit to get your Core Web Vitals scores along with 20+ other SEO factors. You’ll get prioritized recommendations so you know exactly what to fix first.

The sites investing in user experience through strong Core Web Vitals are being rewarded with better rankings. Make sure yours is one of them.

Tags:

core web vitals LCP INP CLS page speed optimization technical seo

Share this article:

Ready to improve your SEO?

Get a free SEO audit and see exactly what needs fixing on your site

Start Free Audit