A Melbourne optometry practice had a live Wordpress site that felt slow: around twelve seconds to become useful on a phone. No staging, a redesign already underway, and a paid-ad landing page that couldn’t go down. Here’s how you fix a site you’re not allowed to break.
What made this job interesting wasn’t the slowness as slow WordPress sites are common, and the usual suspects (caching, images, too many plugins) are well understood. What made it interesting was everything I wasn’t allowed to touch. The site was live and taking real customers, there was no staging copy to experiment on, and a redesign was already in progress across roughly eighteen draft pages all built on a stack of Elementor add-ons which meant deactivating a plugin to “clean things up” could quietly break work that hadn’t shipped yet. And one page, a landing page behind a paid advertising campaign with live enquiry forms, simply could not go down or misbehave even for a minute (as per the client’s directive).
So the real brief was narrower than “make it faster.” It was: make it measurably faster…on the live site…without a net…without disturbing the redesign…and without ever putting the ad campaign at risk. Every change had to be reversible and I had to be able to prove what each one did.
Those first three are the numbers a shared server can’t distort, and I’ll be candid later about which mobile figures it can. The durable wins here are the working cache and the early-loading hero image; they’re real regardless of what any single speed test happens to print. Below is how each one was won, including the one deeper technique I built, tested on the live site, and then deliberately walked away from.
What The Numbers Actually Said
Before changing anything I locked down a baseline: server response times measured directly against the origin plus page-speed lab runs for both mobile and desktop. You can’t claim an improvement you never measured and on a site like this the “before” number is the only thing that keeps you honest later.
The baseline told a clear story. First thing to note was that the server itself was slow to respond. The technical name for this is time to first byte: the gap between a browser asking for the page and the server sending back the very first byte of it. Here that gap was about 3.38 seconds. For those three-plus seconds the visitor’s browser has nothing to work with at all and the page can’t even begin to appear until the server starts answering. The cause was the broken cache I describe below: rather than handing over a ready-made copy of the page, the server was reassembling the entire thing from PHP code and database queries on almost every single visit. The homepage weighed 6.13 MB and pulled in over a hundred separate files. On desktop the site scored 59–62 out of 100. And on mobile, the largest piece of content, the hero banner people came to see, didn’t finish painting until 16.4 seconds in.
None of these are exotic, but the shape of the site (the redesign, the ad page, the shared server) meant the safe way to fix each one mattered more than the fix itself.
The Cache That Wasn’t A Cache
I started with the least glamorous work because it was also the safest and it unblocked everything else. I collapsed the duplicated cache setting down to a single correct one, turned off the debug and 404 logging that was writing to disk and database on every miss, and truncated the bloated error log.
On its own, this changed nothing a visitor would feel. What it did was clear a jammed, self-cancelling cache state so that real caching could finally work.
Then I installed a proper page cache (that’s the only plugin I added to the site the entire engagement). I set it up in its “expert” mode where finished pages are served as static files by the web server before WordPress even wakes up. A first, simpler configuration only reliably cached the homepage on this particular host, so I moved to the more robust setup and hand-built the configuration to get full-site caching working correctly.
Crucially, I told the cache to never touch the pages that must always be live and personal: the ad landing page, the contact and bookings pages, the cart, checkout, and account pages, plus the usual guards so nothing gets cached for a logged-in user or a shopper mid-purchase.
The effect was the biggest server-side win of the project. The homepage’s cold response time dropped from 3.38 seconds to about 1 second, and inner pages went from two-to-three-second rebuilds to being served as roughly one-second static files.
Six Fonts That Didn’t Exist
The font errors turned out to have a tidy root cause: a couple of stale settings records deep in Elementor still pointed at those deleted self-hosted font files. I removed the bad records which let Elementor fall back to loading the fonts correctly from Google’s servers and regenerated the site’s stylesheets.
The font errors went from six to zero on every page I tested and because each of those errors had been dragging in a full megabyte-sized error page, the homepage’s weight dropped from around 5 MB to 3.03 MB in one move. The desktop score jumped to 80.
Notably, this did not move the mobile figure; the fonts it turned out weren’t what was holding mobile back, which pointed straight at the next problem.
The Image The Browser Found Too Late
The mobile bottleneck was that late-discovered hero image, and it’s worth being clear this is a different delay from the slow server response fixed earlier. The caching work sped up how quickly the server starts sending the page. This is about what the browser does after the page has already arrived: even with a fast server, the browser was taking roughly four seconds too long to notice the hero image and begin downloading it, because it was buried in a styling file instead of the HTML. The fix was to add a single, targeted instruction, on the homepage only, telling the browser to fetch that specific image immediately and at high priority, instead of stumbling onto it seconds later. I delivered this as a small, self-contained piece of code that could be removed by deleting one file.
This was the biggest mobile win. The hero’s paint time fell from about 16.4 seconds to roughly 7–11 seconds as the wasted “waiting to even start” delay was gone.
Styling For Widgets That Weren’t There
The last band of work was trimming styling code the site was loading but not using, and trimming it carefully because “unused” is easy to assume and expensive to get wrong on a page built from add-ons.
Rather than guess, I built a live preview switch so the client and I could look at the real homepage with each add-on pack’s styling turned off, one at a time. One pack, Element Pack, turned out to contribute exactly zero used widgets: with its styling off, the page was pixel-for-pixel identical. Another, ElementsKit, broke layouts and icons the moment it was disabled, so it stayed. I permanently suppressed the unused pack’s styling for regular visitors only (editors and the redesign kept everything) by filtering it out at the page level without deactivating or altering the plugin itself. That removed several seconds of render-blocking styling with no visible change.
I did the same conservative trim on some WooCommerce styling (removed only on pages that show no shop elements, left fully intact on cart, checkout, and product pages) and on the fonts. A scan of the site showed it was loading six font families at eighteen weight-and-style variants each, while actually using only eight. I rewrote the font requests down to just those eight, cutting one font’s styling from 20.5 KB to 9.6 KB, and the set of six from around 120 KB to about 55 KB.
By the end, the desktop score sat at 86, with the homepage at roughly half its original weight.
The Honest Part: Mobile Scores On A Shared Server
Here’s where I have to be straight about the numbers because the site’s environment demands it.
This site shares a single server cage with three other WordPress sites. When those neighbours get busy, mobile speed-test scores swing wildly for reasons that have nothing to do with this site. During testing I watched one mobile metric (total blocking time) leap from 130 milliseconds to 6,650 milliseconds inside the same two-minute window. The site hadn’t changed at all in those two minutes; that swing was a CPU-contention lottery being played by four sites on one box.
So I report mobile as a range, hero paint around 7–11 seconds and never as a single triumphant number because a single number here would be dishonest. The trustworthy marks are the ones the shared server can’t distort: the server response time (3.4s → ~1s), the halved page weight, the desktop LCP under two seconds, and the desktop score in the 80s. The durable mobile wins (the working cache and the early hero image) are real regardless of what any one test happens to print.
The Technique I Built, Tested, And Threw Away
There’s a well-known “next level” for this kind of work called critical CSS: you inline just the styling needed for the top of the page and defer the rest, so the first paint needs no network styling at all. It’s the obvious next lever, and it’s exactly the sort of thing you’re tempted to promise.
I’d initially flagged it as something that would need a staging environment but rather than assume, or worse, bill for a guess, I actually built it and tested it safely on a hidden preview of the live homepage that no real visitor could reach.
The result was decisive, and negative. My tooling found that only 8.5% of the megabyte of render-blocking styling was actually used on the homepage which was promising on paper, but every attempt to ship only that “critical” slice broke the top of the page: the header and hero vanished and the service sections collapsed into a narrow column. As a control, I inlined all of the styling and the page rendered perfectly, which proved my method was sound and the problem lay in the site itself. The reason: the top of this homepage is built almost entirely from those Elementor add-ons whose layout styling lives in enormous shared files. One add-on’s stylesheet alone is 469 KB. To render the top of the page correctly, the “critical” set has to include nearly all of the styling, so there’s no meaningful slice to isolate and no win to capture.
That test earned three conclusions worth more than a hollow win.
-
Hand-rolled critical CSS isn’t viable on the current site.
-
Staging would not have changed that because the blocker is the page’s structure, not the test environment.
-
And the genuine fix is the redesign already underway, which, by consolidating those overlapping add-ons, will make this technique worthwhile afterward.
The preview was fully removed the moment testing was done leaving no trace on the live site.
Where A Safe First Pass Ends
So the first pass ended where it should: server response cut by two-thirds, page weight halved, desktop firmly in the 80s, mobile hero paint roughly halved, six page-blocking errors gone, and the background waste stopped. All of it reversible and measured with the redesign and the ad campaign untouched throughout.
The Lesson Under The Lesson
The satisfying trap in performance work is the “heroic” rewrite, the clever technique that promises to cut the number in half. This engagement was a reminder that the real gains usually come from making the boring things actually work: a cache that isn’t cancelling itself out, fonts that exist, an image the browser can find on time. The measure of the work turned out to be the technique I built, tested, and had the discipline to throw away once the evidence said it wouldn’t help yet.
A fast website on a crowded server mid-redesign with a live campaign running isn’t made by adding more cleverness. but is rather made by fixing what’s broken, proving each change, and knowing exactly where to stop.
Client details have been anonymized and certain identifying specifics altered.
Related Reading
-
Case Study: The Overcached Website
A marketing agency's WordPress sites ran up to four caching layers and cached almost nothing; the fix was subtraction, not addition, letting one cache do its job instead of many fighting each other. -
Case Study: Proving a WordPress Staging Site Is Production-Ready
A WordPress staging site looked functional, but profiling exposed a failed page cache, a plugin adding 30 seconds to admin requests, and a PHP memory crash. -
Case Study: How to Audit WordPress Plugins Before a Server Migration
A plugin audit uncovered database bloat, orphaned integrations, and a shared server already running near its limit. -
Security and Speed: Why Fast, Safe Websites Win More Customers
A broader look at why site speed, reliability, trust, and conversions are tied together for small business websites.