WordPress speed optimisation

WordPress speed optimisation that starts with what is actually slow.

Most speed services install a caching plugin, run Lighthouse until the number goes green and send you the screenshot. That is not the work. The work is finding the one thing making your real visitors wait, on their phones, on their connection, and fixing that specific thing. Then proving it moved.

Reply within one working dayFixed scope before any workRollback path on every change

The honest version

Fast and wrong is worse than slightly slower and correct.

A page served from cache in 80ms that shows last Tuesday's price is not a fast site. It is a broken one that happens to load quickly.

What most people optimise

  • A lab score on the homepage, on desktop, with the cache warm
  • Whatever the plugin's checklist happens to list
  • Minification and concatenation, applied to everything, including the things that then break
  • Deferring scripts until something important breaks, then undoing it

What actually moves the numbers

  • Field data from real visitors, split by template and by device
  • The single largest element on the page, and why it loads when it does
  • Caching layers that agree with each other, and clear together
  • Images generated properly once, rather than resized in the browser on every visit

Scope

What we look at to speed up WordPress.

Your site will not need all of it. Part of the job is telling you which parts to leave alone.

Multi layer caching

CDN edge cache at Cloudflare, OPcache for compiled PHP, Redis or Object Cache Pro for the object cache, and application level transients. Each layer helps. Each layer is also somewhere your content can get stuck.

Invalidation, properly

The part nobody sets up. When an editor changes something, every layer holding a copy of that content has to hear about it, including deletions, including pages that query content rather than list it. Get this wrong and you ship a fast site that lies.

Image pipeline

AVIF and WebP with sensible fallbacks, real srcset and sizes so phones stop downloading desktop images, the right set of generated sizes, and bulk regeneration through WP-CLI so your existing library is fixed too, not just new uploads.

Render blocking and critical path

What has to be in the head, what can wait, and what should never have been loaded on that template at all. Fonts, third party tags and plugin assets enqueued site wide for one page that uses them.

FOUC and load flash guards

Deferring CSS naively gives you a flash of unstyled content, and lazy loading the hero gives you a blank box where your headline should be. Both look like a broken site to a visitor and both hurt the metric you were trying to fix. There are correct ways to do this.

Server and origin

Time to first byte on genuinely uncached requests, PHP version and workers, slow queries, Nginx and Apache config, and whether the origin is even involved in your problem. Often it is not.

The part nobody else builds

Four cache layers, and a cache that invalidates itself.

Turning caching on is easy. The hard part is a cache that clears itself correctly the second an editor changes anything, and that does not throw away the work it exists to protect while doing it. This is what runs on a high traffic directory site we own end to end.

The stack

Four layers, in request order

  • CDN edge. HTML held at the edge for 24 hours with s-maxage, so most visitors never reach the origin at all.
  • OPcache. Compiled PHP kept in memory, so the server is not re-parsing files on every request.
  • Redis object cache. Object Cache Pro, holding the query and option results that would otherwise go to MySQL.
  • Transients. Rendered HTML fragments for the expensive parts of a template.

Live headers on that site: browser 2 hours, edge 24 hours, static assets 1 year. Each layer earns its place. Each layer is also somewhere a change can get stuck, which is why the invalidation matters more than the caching.

The expensive mistake

Purge everything was cooling the image cache

The implementation we inherited called a full purge on every content change. It looked safe. It was not. Every edit also wiped the one year image cache, so the AVIF files were perpetually cold. Seven of eight profile pages we sampled were returning MISS.

Nobody had connected the two. The purge logic was filed under correctness, the image loading was filed under performance, and they were the same bug. Rewriting it as a targeted purge by URL list, HTML only, fixed the image performance as a side effect.

Images are now never flushed. Only the HTML that changed is cleared.

Triggers

Wired to every way state actually changes

  • Post save, and post status transitions
  • Meta added, meta updated, and meta deleted. Turning a flag off deletes the meta, so neither the add nor the update hook ever fires
  • Comment events
  • Location terms purged in both URL forms, because some are canonical at the root while the taxonomy form 301s to them
  • Curated landing pages that query content rather than being taxonomy archives, enumerated separately because no term loop can ever reach them. That lookup is itself cached for 12 hours so it does not run on every save

Restraint

Built so it cannot become the problem

  • Rate limited to one purge per 60 seconds
  • Chunked at 30 URLs per API call
  • Run non-blocking on shutdown, so an editor pressing publish never waits on a CDN
  • Image operations purge too. Optimisation plugins that write metadata directly are hooked through their own action, attachment IDs are resolved to every generated size, and purging a source jpg also evicts the AVIF derived from it
  • A rename purges the old URL and the post HTML it was attached to

Verified rather than assumed: six curated pages that had been 14 to 24 hours stale matched the origin immediately afterwards, with edge ages of 43 to 47 seconds.

Image pipeline on the same site

Roughly 9,676 images rebuilt into a custom three size plus AVIF pipeline. Static assets serve with a one year max-age and return cache HIT. Profile images serve as AVIF with cf-cache-status: HIT, staying warm for around 19 hours and beyond, because nothing flushes them any more.

Evidence

One cache failure, four separate causes.

This is the job in miniature. Not a plugin setting, four unrelated faults that each broke the same thing.

Symptom

Six pages stale for a full day

Editors changed content and the live site kept serving the old version, for 14 to 24 hours at a time. The site was fast. It was also showing people the wrong thing, which is the failure mode nobody monitors for.

First step was proving where the fault lived. We requested the origin directly with a cache buster. The origin was correct, so the file was fine and the cache was the problem. That one test ruled out an entire branch of guessing.

Diagnosis

Four causes, not one

  • URLs missing from the purge list entirely
  • A meta deletion path with no hook attached, so removing a value purged nothing
  • A dual URL form for location terms, meaning one of the two live URLs was never cleared
  • Curated pages that query content rather than being taxonomy archives, so no term based purge loop could ever reach them

After the fix, all six pages matched the origin immediately.

How the diagnosis actually goes

Our first theory was wrong, and the data said so.

Card images flashed a placeholder while people scrolled. It looked exactly like a cold cache, so that is what we blamed first.

The theory we had to drop

It was not the cache

We sampled card images at scroll depths 1 to 80 rather than arguing about it. Every single one was a cache HIT, already AVIF, and between 6 and 25 KB. The bytes were not the problem and neither was the delivery.

If we had trusted the first instinct, we would have spent a day tuning a cache that was already working perfectly.

The real cause

Native lazy loading firing too late

The browser was deciding to fetch those images only once they were nearly on screen. Correct behaviour, wrong moment. We added an IntersectionObserver that promotes upcoming lazy images to eager about 1.5 screens ahead, with a rootMargin of 1200px, and a MutationObserver so images inserted by AJAX get the same treatment.

Images far down the page stay lazy, so the bandwidth saving is kept. Only the ones about to be needed are promoted.

Core Web Vitals

Field data, not a screenshot.

Numbers from the same site, measured as field data in CrUX on throttled mobile. This is what the architecture above produces on a template with thousands of images behind it.

Gallery template

372ms

LCP. INP 51ms. The heaviest template on the site, and the fastest, because the images are pre-generated and the HTML is already at the edge.

Homepage

1032ms

LCP, roughly one second. INP 78ms. Slower than the gallery, and we know exactly why, which is the point of measuring per template rather than per site.

Layout shift

0.00

CLS across the home, gallery and profile templates. Nothing moves under the visitor's thumb. Profile template lab LCP is 1528ms with CLS also at 0.00.

Lighthouse SEO on the same site scores 100 out of 100. We mention it last on purpose. It is the least interesting number here.

LCP

Usually the hero image or a heading held up by a font. The fix is rarely "compress the image", it is working out why the browser only discovered that resource halfway through the load.

CLS

Images without dimensions, banners injected above content, and web fonts swapping in at a different size. Cheap to fix and very visible to the person using your site.

INP

The one that replaced FID and the one that catches plugin heavy sites out. Long tasks on the main thread from tag managers, sliders and anything that binds to every element on the page.

Measured on your real templates before and after, per device. If a change did not move the field number, we will tell you that too.

Fit

Worth reading before you enquire.

Good fit if

  • You are failing Core Web Vitals in Search Console and the plugin settings have not fixed it
  • The site is fast on some pages and slow on others and nobody knows why
  • Editors publish changes that do not appear, or appear hours later
  • You have a custom or heavily modified theme, WooCommerce, or a lot of plugins
  • Someone told you to buy a bigger server and you want that checked first

Do not hire us if

  • You want a specific Lighthouse number as the deliverable. We will not optimise for the test instead of the visitor
  • You are on cheap shared hosting and unwilling to change anything about it. There is a floor and no amount of tuning gets under it
  • Your site is small, already loads in under two seconds and just does not score well on a lab test. Save your money
  • You want the theme swapped for a page builder. That is usually how sites end up here

How it runs

Measure, isolate, fix, measure again.

STEP 01

Baseline from real users

Field data by template and device, plus a trace of what your slowest real page is actually waiting on. Not one Lighthouse run on the homepage.

Free first look
STEP 02

Isolate the cause

Origin tested directly with a cache buster, layers checked one at a time. The point is to know which component is at fault before touching anything, so the fix is a decision rather than a guess.

You approve the plan
STEP 03

Implement and verify

We write it, deploy it and check it live, including that content still updates when it should. Rollback path tested in advance on anything that touches caching.

Before and after, per template

Cost

What it costs.

Performance work runs at £90 to £110 an hour. Where it is mixed with development it is billed at a £95 blended rate.

Diagnosis only

From £450

What is slow, why, and in what order to fix it, with the evidence. Around 4 to 6 hours. Hand it to any developer, including one of your own.

Fix the specific thing

Scoped

Caching rebuilt and invalidation made reliable, or the image pipeline sorted, or the critical path cleaned up. Fixed scope and cost agreed in writing first.

Ongoing

Monthly

For sites that keep changing, where a new plugin or a new template can undo the work. Field data watched, regressions caught before Search Console tells you.

Time is logged as it is spent and the ledger is open to you, including anything scrapped or reverted. Full breakdown on the rates page.

FAQ

Questions clients actually ask

Will you get me a 100 PageSpeed score?

Probably not, and we would not chase it. A 100 in Lighthouse is a lab result from a simulated phone on a simulated connection. Google ranks on field data, which is what real visitors on real devices actually experienced over the last 28 days.

We have seen sites score 98 in the lab and still fail Core Web Vitals in the field, usually because the lab run never loads the consent banner, the chat widget or the logged-in state. We optimise for the field numbers. The lab score usually improves anyway, it is just not the target.

Is a caching plugin enough?

Sometimes, and if that is genuinely your situation we will say so rather than sell you a project. A small brochure site on decent hosting with a page cache and sensible images is often fine.

Where it stops being enough is when you have a cart, a login, a search, personalised blocks or content that changes through the day. Then the interesting question is not what gets cached, it is what happens when it needs to stop being cached.

What is the most common thing you find?

Caching that works beautifully until something changes. Purge rules that cover the obvious URLs and miss the awkward ones. Nobody notices for months because the site is fast and looks correct, and the stale pages are the ones nobody on the team visits.

After that, it is images. Enormous originals scaled down in CSS, no AVIF, no proper srcset, and a hero image that is the largest element on the page loading last.

Do I need to move host?

Usually not, and we will tell you before you spend money on it. We once proved a site under a sustained attack was running at 7.5% CPU, which meant the bigger server the client was about to buy would have fixed nothing.

If your origin genuinely is the bottleneck, you will see it in the time to first byte on uncached requests and in the PHP worker queue, not in a Lighthouse screenshot. That is a measurable thing, so let us measure it before you migrate anything.

You keep writing optimisation with an s. Which is right?

Both. We are in the UK so we write optimisation, and plenty of people search for wordpress speed optimization service with a z. It is the same work either way, and Google has understood that for years.

Can you break my site doing this?

Speed work touches caching, asset loading and image handling, so yes, done carelessly it can break layout or serve people the wrong content. That is why nothing risky goes live without a tested rollback path, and why we check the result on your real pages rather than declaring victory from a score.

If the work involves a theme build, it runs through Git so any change can be reverted cleanly.

Book me

Tell us which page feels slow.

Send us a URL and what you are seeing. We will tell you what is actually holding it up, whether it is worth paying to fix, and whether you need us at all.

Reply within one working day No obligation Your details stay with us

Takes about 60 seconds. No newsletter and no CRM sequence. Your details are used to reply to this enquiry and nothing else.