Skip to content
Back to Blog
Performance11 min read

How to Improve Core Web Vitals: 6 Fixes That Work

Step-by-step fixes—lazy loading, code splitting, CDN tuning—that measurably improve LCP, INP, and CLS scores on real hosting environments.

Written by Abdul AbrorTechnical Hosting Support Engineer
How to Improve Core Web Vitals: 6 Fixes That Work
On this page

Core Web Vitals are Google's answer to a simple question: does your site feel fast to a real person? Three metrics—Largest Contentful Paint, Interaction to Next Paint, and Cumulative Layout Shift—score loading speed, responsiveness, and visual stability. Miss the thresholds and your search rankings suffer. Hit them and users stick around longer.

I've spent years troubleshooting slow WordPress installs and misconfigured VPS setups for hosting clients. The same handful of fixes deliver most of the improvement. Below are six changes that move the needle—no guesswork, no fluff.

1. Lazy load images and iframes

Lazy loading defers offscreen images until the user scrolls near them. The browser skips downloading a 2 MB hero image at the bottom of your page until someone actually reaches it, which speeds up initial paint and lowers LCP.

Modern browsers support native lazy loading:

<img src="hero.jpg" alt="Site banner" loading="lazy" width="1200" height="630">
<iframe src="https://www.youtube.com/embed/VIDEO_ID" loading="lazy"></iframe>

Add the loading="lazy" attribute to every <img> and <iframe> outside the viewport on page load. Keep your above-the-fold hero image eager-loaded—lazy loading it will delay LCP.

For WordPress, most caching plugins (W3 Total Cache, WP Rocket, LiteSpeed Cache) include a lazy-load toggle. Turn it on and exclude any image in the first screen. Check the rendered HTML to confirm the attribute appears.

In my support queue I've seen LCP drop by a full second after enabling lazy loading on a product page with twenty thumbnail images. It's a one-line change that pays off immediately.

2. Serve next-gen image formats with fallback

JPEG and PNG files are heavy. WebP and AVIF compress the same visual quality into 30–50% smaller files, which directly lowers LCP by reducing download time.

Use the <picture> element to serve WebP or AVIF to browsers that support them, with a fallback:

<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero image" width="1200" height="630">
</picture>

On a cPanel shared host you can convert images with ImageMagick:

convert original.jpg -quality 85 original.webp

For WordPress, install the WebP Converter for Media plugin or enable WebP generation in your CDN. Cloudflare's Polish feature auto-converts images to WebP when the visitor's browser supports it—turn on Lossless or Lossy mode in the Speed tab.

A client's WooCommerce site dropped LCP from 3.8s to 2.1s after switching product images to WebP and setting proper width/height attributes. The bandwidth bill also fell by a third.

3. Split and defer JavaScript

JavaScript blocks rendering. A single 400 KB bundle forces the browser to download, parse, and execute it before painting content. That crushes both LCP and INP.

Code splitting breaks your bundle into smaller chunks loaded on demand. If you're using Webpack, enable dynamic imports:

// Instead of:
import heavyLibrary from './heavyLibrary';

// Use:
const heavyLibrary = await import('./heavyLibrary');

Webpack will create separate chunks and fetch them only when the code path runs. For a React or Vue app, lazy-load route components so the home page doesn't download the admin panel code.

Defer non-critical scripts with the defer attribute:

<script src="analytics.js" defer></script>

Deferred scripts download in parallel but execute after the DOM is ready. Never defer scripts that render above-the-fold content—you'll delay LCP instead of fixing it.

WordPress sites often load a dozen third-party scripts (Google Analytics, social widgets, chat plugins). In the support tickets I handled, the worst offender was always an old jQuery plugin loading synchronously in the <head>. Move those to the footer and defer them. Most page builders and optimization plugins have an asset cleanup tool that lets you disable scripts per page.

4. Enable text compression and cache static assets

Uncompressed HTML, CSS, and JS waste bandwidth and slow down LCP. Gzip compresses text files to 20–30% of their original size; Brotli does better at higher CPU cost.

On Apache (most cPanel hosts), add this to .htaccess:

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

On Nginx, enable Gzip in your server block:

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

For Brotli, install ngx_brotli and add:

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

Set long cache lifetimes for static assets. In Apache:

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/jpg "access plus 1 year"
  ExpiresByType image/png "access plus 1 year"
  ExpiresByType text/css "access plus 1 month"
  ExpiresByType application/javascript "access plus 1 month"
</IfModule>

In Nginx:

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

Browsers will cache these files locally and skip re-downloading them on repeat visits, which improves LCP for returning users.

5. Configure your CDN for optimal cache hit ratio

A CDN caches static files at edge locations near your visitors. When configured correctly it offloads origin requests and lowers latency—bad configuration just adds a DNS hop without caching anything.

Cloudflare, BunnyCDN, KeyCDN, and others require you to define what gets cached and for how long. In Cloudflare's Page Rules, create a rule matching *.css, *.js, *.jpg, *.png, *.woff2 and set Cache Level to "Cache Everything" with an Edge Cache TTL of one month.

Purge the cache after deploying new assets. Most CDNs offer a purge-by-URL API or a wildcard purge.

Enable HTTP/2 or HTTP/3 in your CDN settings. HTTP/2 multiplexes requests over a single connection, so the browser can download CSS, JS, and images in parallel without opening six separate TCP connections. HTTP/3 does the same over QUIC, which recovers faster from packet loss.

Check your cache hit ratio in the CDN dashboard. Aim for 85% or higher. A low ratio means requests are bypassing the cache and hitting your origin every time—usually because query strings or cookies prevent caching. Strip unnecessary query strings and exclude only the cookies your app actually needs (session ID, cart state) from the cache key.

One client's WordPress site had a 40% hit ratio because every page load appended a random cache-busting parameter to CSS files. Removing that parameter and versioning assets in the filename instead (style.v2.css) pushed the ratio to 92% and cut LCP in half.

6. Reserve space for ads, embeds, and dynamic content

Cumulative Layout Shift measures how much visible content moves during page load. An ad loading two seconds late and shoving your paragraph down the page creates a terrible user experience and a high CLS score.

Reserve space by setting explicit width and height attributes on every image, video, and iframe:

<img src="banner.jpg" alt="Banner" width="728" height="90">
<iframe src="ad.html" width="300" height="250"></iframe>

The browser allocates the correct box size before the asset downloads, so nothing shifts when it arrives.

For responsive images, use the aspect-ratio CSS property:

img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
}

If you're loading third-party ads or embeds (Google Adsense, YouTube, Twitter cards), wrap them in a container with a fixed aspect ratio. A common pattern:

<div style="position: relative; padding-bottom: 56.25%; height: 0;">
  <iframe src="https://www.youtube.com/embed/VIDEO_ID" 
          style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" 
          frameborder="0"></iframe>
</div>

Avoid inserting content above existing content after page load. If you must (a cookie banner, a notification bar), render it with CSS position: fixed so it overlays instead of pushing.

Web fonts cause layout shift when the fallback font and the custom font have different metrics. Use font-display: swap and match the fallback font's size with the size-adjust property:

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

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

This minimizes the layout jump when the real font loads. Tools like Fontaine can calculate the correct override values automatically.

Measuring before and after

Run a baseline test before making changes. Use Google PageSpeed Insights, Chrome's Lighthouse panel, or WebPageTest with a throttled connection. Record your LCP, INP, and CLS scores.

Make one change at a time and re-test. LCP is usually the easiest to fix (optimize images, enable compression, add a CDN). CLS takes more detective work—inspect the page with Chrome DevTools' Layout Shift visualization to see exactly which elements are moving.

INP replaced First Input Delay in early 2024 and measures responsiveness across the entire page lifecycle, not just the first click. Long-running JavaScript, render-blocking CSS, and slow event handlers all hurt INP. Use the Performance panel in DevTools to profile interactions and identify slow tasks. Break them into smaller chunks or move heavy work to a Web Worker.

Field data matters more than lab data. PageSpeed Insights shows both—the lab score comes from Lighthouse's simulated test; the field score comes from real Chrome users visiting your site over the past 28 days. If lab looks great but field looks bad, you have a mobile performance problem or a slow user segment you're not testing against.

FAQ

Do Core Web Vitals affect search rankings?
Yes. Google confirmed in 2021 that Core Web Vitals are a ranking signal. Sites that meet the thresholds get a small boost; sites that fail may rank lower, especially on mobile.

What are the passing thresholds?
LCP under 2.5 seconds, INP under 200 milliseconds, CLS under 0.1. Google measures at the 75th percentile of field data, so 75% of your real users must hit those numbers.

Can I fix Core Web Vitals without a CDN?
You can improve them, but a CDN gives you the biggest LCP gains for the least effort. Even a free Cloudflare plan helps.

Does switching to a faster host help?
Absolutely. A VPS with NVMe storage and good network peering will serve your HTML faster than a crowded shared host. But if your images are still 5 MB each, hosting won't fix LCP.

Why is my CLS score worse on mobile?
Mobile ads often load late and lack explicit dimensions. Responsive layouts also shift more if you're not setting aspect ratios. Test on a real device or use Chrome's device emulation.

Start with images and compression

If you only have an hour, fix images first—convert to WebP, add lazy loading, set width and height attributes. That alone will move LCP and CLS in the right direction. Then enable text compression and check your CDN cache hit ratio. Those four changes handle most of the low-hanging fruit. Code splitting and INP optimization take more time but matter less if your site is content-heavy rather than app-heavy. Measure, fix, measure again.