Core Web Vitals measure three things: how fast your largest content loads (LCP), how quickly your site responds to user input (FID), and how much your page jumps around during load (CLS). Google uses these metrics in search rankings, and more importantly, they directly affect whether visitors stay on your site or bounce. I've worked through hundreds of performance audits, and the same eight fixes come up over and over.
1. Image optimization cuts LCP by half or more
Largest Contentful Paint usually fails because of images. A 2MB hero image that loads above the fold will tank your LCP every time.
Serve images in modern formats—WebP or AVIF—and make sure they're sized correctly for the viewport. A 3000px-wide image displayed at 800px wastes bandwidth and CPU cycles. Use responsive images with srcset:
<img src="hero-800.webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1200px) 800px,
1200px"
alt="Site hero image"
width="1200"
height="600">
The width and height attributes prevent layout shift. Always include them.
For hosting environments with limited CPU, use an image CDN or service that handles resizing and format conversion automatically. Cloudflare's Image Resizing, Bunny Optimizer, or ImageKit work well. If you're running WordPress, ShortPixel or EWWW Image Optimizer can automate compression and WebP conversion.
Preload your LCP image if it's not in the initial HTML. Add this to your <head>:
<link rel="preload" as="image" href="hero-800.webp" fetchpriority="high">
That tells the browser to fetch it immediately instead of waiting for CSS or JavaScript to trigger the load.
2. Lazy loading keeps non-critical images out of the way
Don't lazy-load above-the-fold images. That's the most common mistake I see.
Browsers support native lazy loading now, so for everything below the fold:
<img src="product-photo.webp" loading="lazy" alt="Product detail" width="600" height="400">
For WordPress, most caching plugins and performance tools add loading="lazy" automatically. Check that they're not applying it to your hero image or logo.
Iframes—especially YouTube embeds—benefit from lazy loading too:
<iframe src="https://www.youtube.com/embed/VIDEO_ID" loading="lazy"></iframe>
Or use a facade that loads a thumbnail first and only fetches the iframe when clicked. Paul Irish's lite-youtube-embed is a solid choice for this.
3. Font loading strategy stops invisible text and layout shift
Web fonts block text rendering by default, creating a flash of invisible text (FOIT) or a flash of unstyled text (FOUT). Both hurt LCP and CLS.
Use font-display: swap in your @font-face rules:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
}
That renders text in a system font immediately and swaps in the web font once it loads. The brief flash is better than invisible text.
Preload critical fonts to reduce the swap delay:
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
For Google Fonts, append &display=swap to the URL:
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
Host fonts locally when possible. Third-party requests add latency, and Google Fonts requires an extra DNS lookup and connection.
4. JavaScript splitting defers non-critical code
Shipping one giant JavaScript bundle blocks rendering and delays interactivity. Split your code so only the minimum needed for the initial view loads first.
Modern bundlers like Webpack, Rollup, and Vite support dynamic imports:
// Load modal code only when needed
button.addEventListener('click', async () => {
const { openModal } = await import('./modal.js');
openModal();
});
Frameworks like React and Next.js offer code splitting out of the box. Use route-based splitting at minimum so each page loads only its own code.
For WordPress, most page builders and themes load JavaScript everywhere. Plugins like Asset CleanUp or Perfmatters let you disable scripts per-page. Turn off the carousel library on pages without carousels. Turn off the contact form script on the blog.
5. Eliminate render-blocking resources in the critical path
CSS and synchronous JavaScript in <head> block rendering. The browser can't paint anything until these files download and parse.
Inline critical CSS—the styles needed for above-the-fold content—directly in the HTML:
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
.hero { min-height: 400px; background: #f0f0f0; }
</style>
Load the full stylesheet asynchronously:
<link rel="preload" href="/css/style.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/style.css"></noscript>
That preloads the CSS without blocking, then swaps it to a regular stylesheet once it loads.
For JavaScript, add defer or async to non-critical scripts:
<script src="/js/analytics.js" defer></script>
defer runs scripts after HTML parsing; async runs them as soon as they download. Use defer for scripts that depend on DOM order.
What if the layout still shifts during load?
Cumulative Layout Shift happens when elements move after the page starts rendering. Reserve space for images, ads, and dynamic content so they don't push other elements around.
Set explicit width and height on every image and video. Modern browsers use aspect-ratio automatically, so images take up the right space even before they load.
For ads and embeds, wrap them in a container with a fixed aspect ratio:
.ad-container {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
.ad-container > * {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Don't inject content above existing content without user action. If you load a banner or notice at the top, push it in from the top with a slide animation instead of making it pop in and shift everything down.
Web fonts cause layout shift when they swap in and have different metrics than the fallback font. Use the CSS size-adjust property to match fallback fonts to your web font's dimensions. Tools like Fallback Font Generator calculate the values for you.
7. Defer third-party scripts until after page load
Analytics, chat widgets, social media embeds, and ad scripts compete for bandwidth and CPU. They often load synchronously and block interactivity.
Load them after the main content is interactive. Use requestIdleCallback or a timeout:
window.addEventListener('load', () => {
setTimeout(() => {
// Load chat widget
const script = document.createElement('script');
script.src = 'https://cdn.chatwidget.com/widget.js';
document.body.appendChild(script);
}, 3000);
});
For Google Tag Manager, Segment, or similar tag managers, configure them to load scripts on interaction or after a delay instead of immediately.
Use facades for social embeds. A YouTube embed loads dozens of requests; a static thumbnail with a play button loads one image. Swap to the real embed only when clicked.
8. Server response time sets the floor for everything else
Time to First Byte (TTFB) measures how long the server takes to respond. If your server takes two seconds to send HTML, no amount of front-end optimization will make LCP fast.
Enable caching. For WordPress, install WP Rocket, LiteSpeed Cache, or W3 Total Cache. For static sites, use a CDN like Cloudflare or BunnyCDN to serve cached pages from edge locations.
Check your server's resource usage. High CPU or memory usage, slow database queries, and disk I/O bottlenecks all increase response time. In cPanel, WHM, or your hosting control panel, review resource graphs and enable slow query logging.
Upgrade your hosting if you're on shared hosting with hundreds of sites per server. A VPS or managed WordPress host gives you dedicated resources and typically faster storage.
Use a CDN even if you have caching. Geographic distance matters, and serving static assets from multiple locations reduces latency for users far from your origin server.
For database-driven sites, optimize queries. Install the Query Monitor plugin for WordPress to identify slow queries, then add indexes or refactor the queries. Cache query results when possible.
How do I test Core Web Vitals?
Use Google PageSpeed Insights for lab data and real-user data from the Chrome User Experience Report. For ongoing monitoring, Search Console's Core Web Vitals report shows which pages pass or fail.
WebPageTest gives detailed waterfalls and filmstrip views. Lighthouse in Chrome DevTools runs tests locally and lets you throttle network and CPU to simulate mobile conditions.
Do Core Web Vitals affect search rankings?
Yes, Google includes them in the page experience ranking signal. The impact is modest compared to content relevance and backlinks, but when two pages are otherwise equal, the one with better vitals ranks higher.
Which metric matters most?
LCP is usually the easiest to fix and has the biggest user experience impact. Start there. FID is being replaced by Interaction to Next Paint (INP) as of mid-2024, which measures overall responsiveness instead of just first input.
Can a CDN alone fix Core Web Vitals?
A CDN improves TTFB and can cache images, but it won't fix render-blocking JavaScript, oversized images, or layout shift. You need both edge caching and front-end optimization.
Start with images and server response
If you only fix two things, optimize images and reduce server response time. Those two account for most LCP failures I've diagnosed in support tickets.
Run a test, identify the LCP element, and either compress it, convert it to WebP, or preload it. Check your TTFB—anything over 600ms needs attention. Enable caching, upgrade hosting, or add a CDN.
Then move to JavaScript and CSS: split bundles, defer non-critical scripts, inline critical styles. Fix layout shift last by setting dimensions on images and reserving space for dynamic content. Each fix builds on the previous one, and you'll see scores improve with every change.
