§ · · 5 min read

Core Web Vitals on WordPress: what actually moves the needle

Most WordPress speed advice is plugin roulette. Here's what genuinely shifts LCP, INP and CLS, ranked by impact, from someone who has done this on a lot of sites.

There's a genre of WordPress performance advice that goes: install a caching plugin, install an image plugin, install a "speed optimisation" plugin, run the test again, hope. Sometimes it works. Often you end up with four plugins fighting each other and a site that's slower under real conditions than it was before.

Here's what actually moves Core Web Vitals, roughly ordered by how much impact you get per hour spent.

Understand which metric you're failing first#

The three metrics fail for completely different reasons, and fixing the wrong one wastes a week.

Metric What it measures Usual cause when it fails
LCP Time until the largest visible element paints Hosting, unoptimised hero image, render-blocking resources
INP Responsiveness to interaction Too much JavaScript on the main thread
CLS Layout shifting during load Images without dimensions, injected banners, late-loading fonts

Get this from field data — the Core Web Vitals report in Search Console, or the CrUX section at the top of PageSpeed Insights. That's real Chrome users on real devices.

The lab score underneath it is a simulation. It's useful for debugging because it's repeatable, but it is not what you're being measured on, and chasing a green lab number while field data stays poor is a very common way to waste effort.

Hosting is the ceiling on everything else#

This is unwelcome because it costs money, but there's no way around it: on cheap shared hosting, LCP has a floor you cannot optimise past.

If your server takes 800ms to produce the HTML, that 800ms is in every single page load before a single byte of yours is parsed. No plugin removes it. Time To First Byte is the tax on everything downstream.

Rough targets:

  • TTFB under 200ms — comfortable
  • 200–500ms — workable, worth improving
  • Over 600ms — fix this before touching anything else

Moving from $3/month shared hosting to decent managed hosting routinely takes a second off LCP with no code changes at all. If you're going to spend money on one thing, spend it here rather than on a premium optimisation plugin.

Self-host your fonts#

Google Fonts costs you two extra DNS lookups, two TLS handshakes, a render-blocking CSS request, and then the font files — all before text can paint in the right typeface.

Self-hosting removes the entire chain:

<link rel="preload" href="/fonts/inter-var.woff2"
      as="font" type="font/woff2" crossorigin>
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2");
  font-weight: 100 900;   /* variable font: every weight, one file */
  font-display: swap;
}

Three things worth doing while you're in there:

  • Subset to the characters you use. pyftsubset from fonttools will cut a Latin subset to a fraction of the original.
  • Use variable fonts where they exist, and instance out axes you don't use. On this site, dropping two unused axes from Fraunces took it from 125 KB to 69 KB.
  • font-display: swap. Text renders immediately in a fallback and swaps when the font arrives, instead of sitting invisible.

Bonus: it removes the third-party request that makes Google Fonts a GDPR question in the EU, and it lets you write a much stricter Content Security Policy.

The LCP image is usually the whole problem#

On most WordPress sites the LCP element is the hero image. Four fixes, all of which matter:

Serve modern formats. AVIF where supported, WebP as fallback. A 400 KB JPEG hero is usually 60 KB as AVIF at visually identical quality.

Size it for the viewport. A 3000px-wide image scaled down in CSS still downloads all 3000 pixels. Use srcset and let the browser choose.

Never lazy-load the LCP image. This is the single most common self-inflicted wound in WordPress performance. Lazy-loading plugins apply loading="lazy" to everything including the hero, which delays the exact element being measured. Force loading="eager" and fetchpriority="high" on it.

<img src="hero.avif" width="1200" height="630"
     loading="eager" fetchpriority="high" alt="…">

Always set width and height. The browser reserves the space before the image arrives, which is most of your CLS fixed for free.

Page builders are the INP problem#

If you're failing INP on WordPress, it is very likely the page builder. Elementor, Divi, WPBakery and friends ship large JavaScript bundles that run on every page whether the page uses their interactive features or not.

Honest options, in order of how much they help:

  1. Build the templates properly. A custom theme or block-based build with no builder runtime. Best outcome, most work.
  2. Keep the builder for content pages, hand-build the landing pages. Pragmatic compromise — your highest-traffic pages get the fast treatment.
  3. Dequeue what the page doesn't use. Most builders load everything everywhere; conditionally dequeuing on pages that don't need it recovers real milliseconds.

Also audit your plugins for front-end assets. A contact form plugin loading its JS on all 200 pages, when the form is on one, is extremely common. Query Monitor will show you exactly what each page enqueues, and the list is usually worse than you expect.

Caching, correctly#

Page caching is a genuine win. It's also the step people do first and then stop, which is why sites end up "cached" and still slow.

  • Full-page cache at the server if you can (Nginx FastCGI cache, or your host's built-in), plugin-based if you can't. Server-level skips PHP entirely.
  • Object cache with Redis, if the site does real database work. On a brochure site it changes little; on WooCommerce it's transformative.
  • Long browser cache on static assets. Cache-Control: public, max-age=31536000, immutable on anything with a content hash in the filename.
  • A CDN in front, if your audience isn't local to your server.

Stack one caching plugin. Not three. They conflict, and the resulting behaviour is very hard to debug.

What I'd actually do, in order#

  1. Check field data to find out which metric is failing.
  2. Measure TTFB. If it's over 600ms, fix hosting before anything else.
  3. Fix the LCP image: format, size, eager loading, explicit dimensions.
  4. Self-host and subset fonts.
  5. Audit enqueued assets, dequeue what the page doesn't use.
  6. Add page caching, one plugin or server-level.
  7. Re-measure in the field after 28 days — that's the CrUX reporting window, so faster checks tell you nothing.

That order is deliberate. Most of the impact lives in the first four, and they're the ones people skip because installing a plugin feels more like progress.


Passing Core Web Vitals is included in every site we build rather than sold as an add-on — a site that fails them isn't finished. If yours is slow and you'd rather not spend a month on it, tell us what you're dealing with.

§ · Start

Rather have us do it?

Everything we write about here is work we do. Tell us what you need and you'll get a written scope with a fixed price.