Skip to content
Back to Blog
Performance11 min read

Core Web Vitals Optimization: [Solved] 4 Fixes That Work

Fix LCP, INP, and CLS issues with four tested techniques that work across WordPress, Next.js, and static sites—with real PageSpeed improvements.

Written by Abdul AbrorTechnical Hosting Support Engineer
Core Web Vitals Optimization: [Solved] 4 Fixes That Work
On this page

Google's Core Web Vitals are now a ranking factor, and I've watched hundreds of sites lose traffic because of poor LCP, INP, or CLS scores. The good news? Four specific fixes clear most issues across WordPress, Next.js, and static sites. I've tested these on shared hosting, VPS, and CDN setups—they work.

Understanding what actually matters

Core Web Vitals measure real user experience through three metrics. Largest Contentful Paint (LCP) tracks how fast your main content loads—Google wants under 2.5 seconds. Interaction to Next Paint (INP) replaced First Input Delay in 2024 and measures how quickly your page responds to clicks, taps, and key presses. Target is under 200 milliseconds. Cumulative Layout Shift (CLS) penalizes elements that jump around while the page loads; keep it below 0.1.

Most sites fail on LCP. Second most common failure is CLS, especially on mobile. INP problems usually surface on JavaScript-heavy pages or sites with poor server response time.

PageSpeed Insights will show you lab data and field data. Lab scores are simulated; field data comes from actual Chrome users visiting your site over the past 28 days. Fix the field data first—that's what Google uses for ranking.

Fix 1: Preload and optimize your LCP image

Your LCP element is almost always an image, a background image, or a text block. Run PageSpeed Insights and check the diagnostics section—it will tell you which element triggered LCP.

If it's an image, add a preload link in your HTML head:

<link rel="preload" as="image" href="/hero-image.jpg" fetchpriority="high">

For WordPress, you can inject this through your theme's functions.php:

function preload_lcp_image() {
    echo '<link rel="preload" as="image" href="' . get_template_directory_uri() . '/assets/hero.jpg" fetchpriority="high">';
}
add_action('wp_head', 'preload_lcp_image', 1);

Next.js makes this easier with the Image component's priority prop:

import Image from 'next/image';

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      width={1200}
      height={630}
      priority
      alt="Hero image"
    />
  );
}

The priority flag tells Next.js to preload the image and skip lazy loading.

Now compress that image. Use WebP or AVIF if your hosting supports it. I've seen LCP drop from 4.2 seconds to 1.8 seconds just by converting a 1.2 MB JPEG to a 180 KB WebP. Use tools like Squoosh or ImageOptim locally, or install the ShortPixel or Imagify plugin for WordPress.

Set explicit width and height attributes on every image. Browsers need dimensions to reserve space before the image loads—without them, you'll get layout shifts. WordPress 5.5+ does this automatically for images uploaded through the media library, but manually inserted images and background images still need attention.

Fix 2: Defer and minimize JavaScript execution

JavaScript is the top INP killer. Every script you load competes for the main thread, and while the browser is busy parsing or executing JS, it can't respond to user input.

First, defer non-critical scripts. If you control the HTML:

<script src="/analytics.js" defer></script>
<script src="/chat-widget.js" defer></script>

The defer attribute downloads the script in parallel but delays execution until after HTML parsing finishes. For WordPress, most caching plugins (WP Rocket, Autoptimize, LiteSpeed Cache) have a one-click defer option. Turn it on. Test your site afterward—some plugins break when deferred.

Third-party scripts are the worst offenders. Chat widgets, ad networks, heatmap trackers—they all inject synchronous JavaScript that blocks rendering. In support tickets I handled, removing a single poorly coded chat widget improved INP by 150 milliseconds.

If you must keep a third-party script, load it on user interaction:

let chatLoaded = false;

document.addEventListener('scroll', function loadChat() {
  if (!chatLoaded) {
    const script = document.createElement('script');
    script.src = 'https://example.com/chat.js';
    document.body.appendChild(script);
    chatLoaded = true;
    document.removeEventListener('scroll', loadChat);
  }
}, { once: true });

This delays the chat widget until the user scrolls. You can trigger on mousemove, click, or a timer instead.

For Next.js, use dynamic imports with ssr: false for components that don't need server rendering:

import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), {
  ssr: false,
  loading: () => <p>Loading...</p>,
});

This keeps the component's JavaScript out of the initial bundle.

Fix 3: Reserve space for dynamic content

CLS happens when elements move after the page has started rendering. Common causes: images without dimensions, web fonts swapping in, ads or embeds loading late, and content injected by JavaScript.

Web fonts are fixable with font-display: swap and preloading. Add this to your CSS:

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

Then preload the font file:

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

The swap value shows fallback text immediately and swaps in the custom font once it loads, which can cause a brief layout shift. If your custom font is very different from the fallback, the shift will be noticeable. In that case, match the fallback font's metrics using size-adjust in modern browsers:

@font-face {
  font-family: 'Fallback';
  src: local('Arial');
  size-adjust: 95%;
  ascent-override: 105%;
  descent-override: 35%;
}

Calculating those values is tedious—use a tool like Fontaine or the Font Style Matcher to get the right adjustments.

For injected content (banners, cookie notices, ads), reserve space with a placeholder. If your ad slot is 300×250, add this to your HTML:

<div class="ad-slot" style="min-height: 250px; width: 300px;"></div>

The browser will reserve that space even before the ad loads.

WordPress themes often inject content via JavaScript without reserving space. Check your theme's header and footer templates. If you see AJAX calls or JS that inserts HTML, wrap the target container in a min-height rule.

Fix 4: Enable compression and use a CDN

Slow server response time inflates LCP and INP because the browser can't start rendering until it receives the HTML. Time to First Byte (TTFB) should be under 600 milliseconds. If it's over a second, your hosting is the bottleneck.

Enable gzip or Brotli compression. On Apache with mod_deflate:

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
</IfModule>

For Nginx:

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
gzip_min_length 1000;

Brotli compresses better than gzip but requires more CPU. On Nginx with the Brotli module:

brotli on;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

Most managed WordPress hosts enable compression by default. Shared cPanel hosting often doesn't. Check by running curl -I -H "Accept-Encoding: gzip" https://yoursite.com and look for Content-Encoding: gzip in the response headers.

A CDN caches your static assets (images, CSS, JS) at edge servers close to your users, which drops latency. Cloudflare's free plan works fine for most sites. Point your domain's DNS to Cloudflare, enable the proxy (orange cloud), and turn on Auto Minify and Brotli under Speed settings.

For WordPress on shared hosting, pairing Cloudflare with a page caching plugin (WP Super Cache or W3 Total Cache) can cut TTFB in half. The plugin caches the rendered HTML so PHP doesn't regenerate every page on every request.

Next.js apps deployed on Vercel or Netlify get CDN and compression by default. Self-hosted Next.js on a VPS should sit behind Nginx with caching and compression enabled.

What happens when you ignore Core Web Vitals?

Google started using these metrics for ranking in mid-2021 as part of the page experience update. Sites with poor scores don't get delisted, but all else being equal, a competitor with better vitals will rank higher.

I've seen case studies where fixing LCP from 4 seconds to 2 seconds lifted organic traffic by 15-20 percent over three months. Not overnight magic, but real gains. More importantly, users stay longer and convert better when pages feel fast.

Mobile scores are often worse than desktop. Google's ranking algorithm uses mobile scores, so prioritize mobile when testing and optimizing.

Measuring before and after

Run PageSpeed Insights before making changes and screenshot the scores. Make your fixes, deploy them, and wait 24-48 hours for field data to update. Run the test again.

Lab scores update immediately, but field data is what counts. If your site has low traffic, field data might take weeks to accumulate. In that case, focus on lab scores and use Chrome's Lighthouse panel in DevTools for repeated testing.

Check the Core Web Vitals report in Google Search Console. It shows which URLs are failing and groups them by issue. Fix the most common issues first—those affect the most pages.

For ongoing monitoring, use tools like DebugBear, SpeedCurve, or Treo. They track your vitals over time and alert you when scores regress after a deploy.

Verify your biggest bottleneck first

Start with the metric you're failing hardest. If LCP is 5 seconds, fix that before worrying about INP. Use the diagnostics section in PageSpeed Insights to identify the root cause—it will tell you if the problem is server response time, render-blocking resources, or an unoptimized image.

Test on real devices if possible. Chrome DevTools throttling is useful, but nothing beats loading your site on an actual mid-range Android phone over a 4G connection. That's where most mobile performance issues surface.

Core Web Vitals optimization isn't a one-time task. New plugins, theme updates, and content changes can all regress your scores. Set up monitoring and check your vitals monthly.

FAQ

Will these fixes work on shared hosting?

Yes. The image and JavaScript optimizations work anywhere. Compression and caching depend on your host allowing .htaccess rules or providing a caching plugin. Most cPanel hosts allow both.

Do I need to remove all third-party scripts?

No. Defer them or load them on interaction. Measure the impact of each script and remove only the ones that hurt performance without adding value.

How long until I see ranking improvements?

Google re-evaluates pages as it recrawls them and as new field data accumulates. Give it four to six weeks. Core Web Vitals are one ranking factor among hundreds—they help, but they won't fix poor content or weak backlinks.

Can I fix CLS without removing ads?

Yes, by reserving space for the ad slots. Use fixed dimensions or min-height rules so the page doesn't shift when ads load.

What if my LCP element is a background image?

Preload it the same way you'd preload a regular image, using the full path to the background image file. Make sure the image is optimized and served in a modern format.