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.
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:
| Metric | Measures | Good | Needs Work | Poor |
|---|---|---|---|---|
| LCP | Loading speed | ≤2.5s | 2.5-4s | >4s |
| INP | Interactivity | ≤200ms | 200-500ms | >500ms |
| CLS | Visual stability | ≤0.1 | 0.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:
- Open Chrome DevTools (F12)
- Go to Performance tab
- Click the refresh button to record a page load
- Find the “LCP” marker in the timeline
- Click it to see which element is the LCP
Common LCP elements:
- Hero images
- Featured video thumbnails
- Large text blocks (headings)
- Background images
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:
- Use a CDN - Serve assets from edge locations near users
- Enable compression - Gzip or Brotli compression
- Upgrade hosting - Move from shared to VPS/dedicated hosting
- 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:
- Server-side rendering (SSR) - Render HTML on the server
- Static site generation (SSG) - Pre-render pages at build time
- 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:
- Open DevTools → Performance tab
- Enable “Web Vitals” in settings
- Click record
- Interact with the page (click buttons, submit forms)
- Stop recording
- 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:
- Total DOM nodes: <1,400
- Maximum depth: <32 levels
- Maximum children: <60 per parent
How to reduce DOM:
- Virtualize long lists (render only visible items)
- Remove hidden elements instead of display: none
- Lazy-load below-the-fold content
- 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:
- Open DevTools
- Press Cmd/Ctrl + Shift + P
- Type “Show Core Web Vitals overlay”
- Enable it
- Reload the page
- 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)
- LCP image optimization - Often the single biggest improvement
- CLS dimensions - Quick win with immediate results
- Render-blocking resources - Affects all metrics
Medium Impact
- Server response time - Requires infrastructure changes
- Long tasks - Requires JavaScript refactoring
- Font optimization - Prevents flash of unstyled text
Lower Impact (But Still Important)
- DOM size reduction - Improves overall performance
- Third-party script management - Ongoing maintenance
- Animation optimization - Edge cases
Verifying Your Fixes
After implementing fixes, verify they worked:
Quick Check (Lab Data)
- Run PageSpeed Insights on your URL
- Compare scores before/after
- Lab data updates immediately
Authoritative Check (Field Data)
- Wait 28 days for Search Console data to update
- Check Core Web Vitals report for improvement
- Field data is what actually affects rankings
Continuous Monitoring
- Set up Search Console alerts for CWV issues
- Run periodic audits with Rankture
- Test after every major deployment
Common Mistakes When Fixing Core Web Vitals
1. Only Testing the Homepage
Check your most important pages:
- Top landing pages (from Search Console)
- Key conversion pages
- Template pages (one fix helps all 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:
- Lab data: Immediate
- Field data: 2-4 weeks
- Ranking impact: 4-8 weeks after field data improves
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:
- Core features on high-traffic pages = must fix
- Non-essential features = consider removing or deferring
- Low-traffic pages = lower priority
Can I pass Core Web Vitals with WordPress/Shopify/etc.?
Yes, but platform-specific approaches help. Check our guides:
- Improve Core Web Vitals for WordPress
- Best WordPress Plugin for Core Web Vitals
- SEO Audit Tools for Ecommerce
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:
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