A slow WordPress site is rarely slow for one reason. It's a large hero image, plus an uncached server, plus six plugins adding scripts, plus a web font that shifts the layout. Core Web Vitals give you a way to find which of those is actually hurting visitors, so you can fix that first.
The three metrics, in plain termsLink to this section
| Metric | What it measures | "Good" threshold |
|---|---|---|
| LCP: Largest Contentful Paint | How long until the main content (usually the hero image or headline) is visible | 2.5 s or less |
| INP: Interaction to Next Paint | How quickly the page responds after a tap, click or key press | 200 ms or less |
| CLS: Cumulative Layout Shift | How much the layout jumps while loading | 0.1 or less |
INP replaced First Input Delay as a Core Web Vital in March 2024. Google assesses each metric at the 75th percentile of real visits, so a site passes only when three out of four visits are good, on mobile and on desktop separately.
Measure the right thing firstLink to this section
- Field data comes from real Chrome users. Look at PageSpeed Insights (top section) and the Core Web Vitals report in Search Console. This is what counts for search.
- Lab data is a simulated test. It's the Lighthouse section in PageSpeed Insights and the Performance panel in Chrome DevTools. It's useful for finding why a page is slow.
Test the templates that matter, such as the homepage, a typical post, a product page and a landing page, not just the homepage. In PageSpeed Insights, the "Largest Contentful Paint element" diagnostic tells you exactly which element to optimize.
Fixing LCPLink to this section
LCP breaks down into four parts: server response time (TTFB), the delay before the resource starts loading, the download time and the render delay. Work through them in that order.
1. Server response: cache pagesLink to this section
If TTFB is above roughly 800 ms, no front-end trick will save LCP. Fixes, from biggest to smallest impact:
- Full-page caching through the host's cache, a caching plugin or a CDN that caches HTML.
- A persistent object cache (Redis or Memcached) for dynamic pages such as carts and dashboards.
- A current PHP version and enough PHP workers for your traffic.
- A lean
wp_optionstable. Large autoloaded options slow every uncached request:
SELECT option_name, LENGTH(option_value) AS bytes
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto-on', 'auto')
ORDER BY bytes DESC
LIMIT 20;Leftover data from plugins you deleted long ago often tops this list.
2. Make the LCP image load firstLink to this section
The hero image should start downloading immediately, at full priority:
- Don't lazy-load it. WordPress skips
loading="lazy"for the first content images, but sliders and page-builder backgrounds often bypass that logic. - Use
fetchpriority="high"on it. WordPress 6.3+ adds this automatically to the image it thinks is the LCP. Check that it picked the right one. - Avoid CSS background images for the hero. The browser discovers them late. A real
<img>(or apreloadlink) is found much sooner. - Be careful with hero sliders. Many load several large images and JavaScript before showing the first slide. A single static image usually wins on LCP, and on conversion.
3. Make the image smallerLink to this section
Serve modern formats at the size they're displayed. WordPress generates multiple sizes and srcset for you, provided the theme uses wp_get_attachment_image() or the image block. Since WordPress 6.5, AVIF uploads are supported when the server's image library can handle them.
For one-off assets such as a hero, a logo or a background, convert to WebP before uploading. The image to WebP converter does it in the browser, shows the size saving and never uploads your file.
Fixing INPLink to this section
INP suffers when the main thread is busy running JavaScript when the visitor interacts. In WordPress the usual sources are page builders, sliders, chat widgets, tag managers and several analytics scripts.
- Audit what loads. In DevTools, open the Coverage panel and the Network tab filtered by JS. Every script you can remove is a direct win.
- Load scripts only where they're used. Dequeue a plugin's assets on pages that don't need them:
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_page( 'contact' ) ) {
wp_dequeue_script( 'contact-form-7' );
wp_dequeue_style( 'contact-form-7' );
}
}, 100 );- Defer what you control. Since WordPress 6.3, register scripts with a loading strategy instead of hacking the tag:
wp_enqueue_script(
'theme-ui',
get_theme_file_uri( 'assets/ui.js' ),
array(),
'1.4.0',
array( 'strategy' => 'defer', 'in_footer' => true )
);- Delay third-party widgets such as chat or video embeds until the visitor scrolls or clicks. A static thumbnail that swaps in the real player on click is a classic win.
- Break up long tasks in your own code. Handlers that do heavy work on every keystroke or scroll event should be debounced or moved off the main thread.
Fixing CLSLink to this section
Layout shift is almost always one of four things:
- Images and embeds without dimensions. Every
<img>,<iframe>and video needswidthandheightattributes (or a CSSaspect-ratio) so the browser reserves space. - Web fonts swapping in. Self-host fonts, preload the one or two used above the fold, and use
font-display: swapwith a fallback font of similar proportions. Fluid type sizes built with CSSclamp()also prevent jumps between breakpoints. The CSS clamp calculator generates the values. - Content injected above existing content. Cookie banners, promo bars and ads should overlay or have reserved space, never push the page down after load.
- Late-loading CSS for above-the-fold elements. Keep critical styles in the main stylesheet rather than lazy-loading them.
Try it yourself: see layout shift happenLink to this section
Press Try it to open this example in the live playground. Both "images" arrive after a second. The first has no reserved space and pushes the text down; the second uses aspect-ratio, so nothing moves.
<style>
body { font: 16px/1.5 system-ui, sans-serif; margin: 1rem; display: grid; gap: 1.5rem; grid-template-columns: 1fr 1fr; }
.card { border: 1px solid #ccd; border-radius: 10px; padding: 12px; }
.slot img { display: block; width: 100%; }
.reserved { aspect-ratio: 16 / 9; background: #eef; }
.reserved img { height: 100%; object-fit: cover; }
</style>
<div class="card">
<strong>No dimensions</strong>
<div class="slot" id="a"></div>
<p>This paragraph jumps when the image loads.</p>
</div>
<div class="card">
<strong>Reserved with aspect-ratio</strong>
<div class="slot reserved" id="b"></div>
<p>This paragraph stays put.</p>
</div>
<script>
const svg = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 9"><rect width="16" height="9" fill="#2343c7"/></svg>');
setTimeout(() => {
for (const id of ['a', 'b']) {
const img = new Image();
img.src = svg;
img.alt = 'Demo image';
document.getElementById(id).appendChild(img);
}
}, 1000);
</script>Diagnosing a slow page in ten minutesLink to this section
When a template fails, this routine usually finds the cause before you change anything:
- Open PageSpeed Insights for the URL and note which metric fails in the field data, on mobile.
- Read the diagnostics under "Largest Contentful Paint element" and "Avoid large layout shifts". They point at specific elements.
- Record in Chrome DevTools. Open the Performance panel, throttle the CPU to 4× and the network to "Fast 4G", then reload. Its live metrics view shows LCP, INP and CLS as you click around, and long tasks appear in red on the main-thread track.
- Check the waterfall in the Network tab. If the hero image starts late, it's a discovery problem (lazy-loading, a CSS background or a slider). If it starts early but downloads slowly, it's a size problem.
- Toggle suspects. Block a script's URL in the Network panel ("Block request URL") and reload. If INP improves, you've found the script to defer or remove.
Measuring INP from real visitorsLink to this section
Field data in Search Console tells you that INP fails, but not which interaction is slow. Google's web-vitals library reports the element and timing from real visits. Log it to the console while testing, or send it to your analytics:
<script type="module">
import { onINP } from 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.js?module';
onINP(({ value, attribution }) => {
console.log('INP', Math.round(value), 'ms on', attribution.interactionTarget);
});
</script>The interactionTarget is a CSS selector for the element the visitor used, which turns "INP is 450 ms" into "the filter dropdown on the archive page is slow".
A realistic optimization orderLink to this section
- Get page caching and a CDN working, and confirm TTFB is low on cached pages.
- Fix the LCP element on each key template.
- Remove or delay unnecessary scripts and plugins.
- Add dimensions everywhere and sort out fonts.
- Re-measure. Field data in Search Console takes about 28 days to reflect changes, so note the date of each fix.
Image-heavy business sites, such as exhibition, real-estate and hospitality websites like Nucleus Exhibitions or Ace Ventures, benefit most from steps 2 and 3. Their pages lead with large photography and often with sliders.
When to consider a different architectureLink to this section
If a site still can't pass after caching and clean-up, usually because a page builder outputs heavy markup and scripts on every page, a headless frontend becomes worth evaluating. The headless WordPress with Next.js guide explains the trade-offs.
Frequently asked questionsLink to this section
How much do Core Web Vitals affect rankings?Link to this section
They're part of Google's page experience signals. Relevance and content quality matter far more, so a slow page with the best answer can still rank. Where two pages are close, experience can tip the balance, and faster pages convert better whatever their ranking.
Why does my PageSpeed score differ from Search Console?Link to this section
The score is lab data from one simulated load. Search Console reports field data from real Chrome users over the previous 28 days. A page can score 60 in the lab and still pass in the field, or the other way round. The field data is what counts.
Which caching plugin should I use?Link to this section
Start with the host. Server-level page caching from the host or a CDN is usually faster and simpler than a plugin. Add a plugin only for what the host doesn't do, and never run two page-caching layers that fight each other.
Will a faster theme fix everything?Link to this section
Only if the theme is the problem. Measure first: a fast theme still struggles behind a slow server, a heavy slider or ten third-party scripts.
Performance work also overlaps with SEO and accessibility. Readable contrast, for example, is part of a good page experience; check yours with the contrast checker. If you'd like a performance review of your site with a prioritized fix list, get in touch.
Test yourself
3 quick questions on this lesson.
Finished this lesson?
Mark it complete to track your progress through the learning paths.