Elman Huseynov
Web Performance & SEO

Improve Core Web Vitals on WordPress: How I Got to 100

5 min read

Related service: Technical SEO audit and Core Web Vitals

Episode 26. 56 to 100 on mobile, and what each fix was worth

One WordPress site scored 56 for mobile performance in August 2026 and scores 100 now.

Four findings did most of the work to improve core web vitals here.

A cache plugin lazy-loading the LCP image. A hero portrait sitting below the copy on mobile.

Analytics competing with the page, and an unsubsetted font.

No plugin did this. 2 of the 4 findings that mattered were caused by a plugin.

Where did the score start?

Lighthouse on mobile read 56 performance, 54 best practices, 92 SEO.

The theme shipped a 1.1 MB JavaScript bundle. three.js at about 600 KB, GSAP, a smooth-scroll library, a lightbox, a carousel.

jQuery 3.1.1 from 2016. The full Font Awesome set for a few icons. A 1.8 MB PNG.

Category Mobile before Mobile after
Performance 56 100
Accessibility 98 100
Best practices 54 100
SEO 92 100
Mobile Lighthouse before and after the work to improve core web vitals: performance 56 to 100, with accessibility, best practices and SEO all at 100
WordPress core web vitals: 56 to 100 on mobile

Optimise the old theme or replace it?

The answer was neither. I added a second front-end inside the old theme. A function decides which layer serves each request.

New templates get about 20 KB of hand-written CSS and no jQuery. The old portfolio templates keep their stack.

Splitting the theme cost a day. Rewriting everything would have cost a month and delivered the same score.

How to improve core web vitals on WordPress: the 4 findings

1. The cache plugin was hijacking the LCP image

The largest contentful paint element carried the classes entered litespeed-loaded.

The plugin installed to make the page fast was lazy-loading its most important image. Excluding it was worth about 12 points.

A performance plugin optimises for its own defaults. Your LCP element is exactly the thing it should not touch.

2. The portrait painted last on mobile

On a 360px viewport the hero photo sat at y=598, just inside the fold. So it counted as the LCP element.

Three lines of CSS moved it to the top on 1 column. LCP dropped by about 2 seconds.

@media (max-width: 899px) {
  .hero__portrait { order: -1; max-width: 260px; }
}

3. Analytics in the load window

Google’s tag is 165 KB. It costs 300 to 400 ms of main-thread time on a throttled phone.

Deferring it to idle was not enough. On a fast connection idle is at 1.5 seconds, inside the load window.

The rule that shipped: load on the first pointer, key or scroll event. Fallback, 2 seconds after load.

Total blocking time was 100 to 480 ms before. After, 20 to 120 ms.

4. An unsubsetted font

The monospace face only sets small uppercase labels: dates, category names, section numbers.

Subsetting it to the characters used took it from 40 KB to 14 KB.

pyftsubset geist-mono.woff2 \
  --unicodes="U+0020-007E" \
  --flavor=woff2 --layout-features='' \
  --output-file=geist-mono-labels.woff2

Check the subset covers everything. Mine was missing the tick and cross glyphs, and Chrome silently substituted another face.

Four findings that fixed WordPress core web vitals: a cache plugin lazy-loading the LCP image, a hero portrait below the copy on mobile, analytics in the load window, and an unsubsetted font
The 4 findings that did the work

More ways to improve core web vitals, in order of value

  • Inline the CSS. About 20 KB, minified, in the head. No render-blocking request at all.
  • AVIF with WebP and JPEG fallback. The hero went from 340 KB to 43 KB. This host served AVIF as text/plain without an explicit rule.
  • width and height on every image. Cumulative layout shift went to 0 and stayed there.
  • Delete the tracking pixels. 2 were for campaigns that ended years ago.
  • Cache headers on the HTML. There were none. s-maxage plus stale-while-revalidate fixed the fast-sometimes, slow-sometimes feel.
Further WordPress core web vitals fixes: inline the CSS, AVIF with fallbacks, width and height on every image, delete old tracking pixels, and set cache headers on HTML
The rest, in order of value

What did not improve core web vitals here?

I thought the top progress bar was hurting Speed Index. I delayed it by 400 ms.

Speed Index got worse, from 3.5 s to 6.9 s. Then 3 consecutive runs read 100, 99 and 100.

Measurement variance, and my hypothesis was wrong. Speed Index swings by seconds on a real network; FCP and LCP stay stable.

Never re-tune on 1 run, and never measure during a deploy.

A failed WordPress core web vitals hypothesis: delaying the progress bar made Speed Index worse, and three consecutive runs then showed it was measurement variance
What did not work

What did it take?

The rebuild took 3 days, most of it in the theme rather than in settings.

The second front-end, the 4 findings, and the images produced the score. The plugin toggles I tried first produced nothing.

Settings alone would have left the score near 56.

Common questions

Can a caching plugin make Core Web Vitals worse?

Yes, and it happened here. LiteSpeed’s lazy-load applied itself to the hero image, which was the LCP element. It deferred exactly the thing the page most needed. Excluding the LCP image from lazy-loading was worth about 12 points. Check what your plugin is doing to your largest element.

How long does it take to get a WordPress site to 100?

About 3 days here, most of it inside the theme rather than in plugin settings. The plugin toggles I tried first produced almost nothing. If your theme ships a megabyte of JavaScript, no amount of configuration reaches 100. The work is a front-end rewrite of the templates that matter.

Does a 100 Lighthouse score actually matter for ranking?

Core Web Vitals are a ranking signal and a weak one next to content. The bigger return is fewer people leaving before the page paints. And a site that works on a mid-range phone on mobile data. Chase the field data in Search Console rather than the lab score.

Why do Lighthouse scores change between runs?

Speed Index swings by seconds on a real network. First contentful paint and LCP stay stable. I once delayed a progress bar by 400 ms. Speed Index went from 3.5 to 6.9 seconds. Then 3 consecutive runs read 100, 99 and 100. Never re-tune on 1 run.

How do you speed up a WordPress site?

Find what delays the largest element on mobile, then fix that. Here it was a caching plugin lazy-loading the hero image. An unsubsetted font was the other cause. Adding another plugin is not the fix. Measure field data before and after, not the lab score.

The short version

Find your LCP element and make sure nothing is deferring it, including your cache plugin. Check where it sits on a 360px screen.

Get third-party scripts out of the load window. Subset your fonts and check the subset. Measure 3 times before believing anything.

3 sites, 3 stacks, live scores: the Core Web Vitals and technical SEO page. Same method each time.