Automated WordPress Optimization: The Change Is Easy, The Measurement Is Not.

Three of my own performance measurements returned confident wrong answers in one afternoon. Here is what that means for automating the fixes.

Aditya Sharma·10 min read

Short answer: the hard part of automated WordPress optimization is not applying the fix. It is being sure the measurement that triggered it was true. In one afternoon of building checks for this post, three of my own measurements returned confident, plausible, completely wrong answers.

Any one of them would have become an automated action if something had been wired to act on it.

What is actually left to automate

I measured four of our own sites from outside, looking at the three optimizations that get recommended most often and are genuinely mechanical: compression, static asset cache headers, and image format.

Table of four sites with TTFB, HTML size, compression, image count and formats. posimyth.com, theplusaddons.com, nexterwp.com and uichemy.com all serve brotli. Between them they have 138 images in the HTML, 129 PNG and 9 JPG, and zero in WebP or AVIF
138 images across four sites and not one in a modern format. The two easy wins were already

done everywhere.*

SiteTTFBHTMLCompressionImagesFormatsWebP/AVIF
posimyth.com786ms19,888Bbrotli1816 PNG, 2 JPG0
theplusaddons.com1,545ms108,508Bbrotli6461 PNG, 3 JPG0
nexterwp.com855ms66,358Bbrotli4945 PNG, 4 JPG0
uichemy.com1,525ms133,625Bbrotli77 PNG0

That result is not what I expected and it changed what this post is about. Compression was already brotli on all four. Static assets already carried a thirty day max-age. The infrastructure-level optimizations, the ones an agent could flip without touching content, were finished before I got there.

What is left is image format, and the WebP and AVIF numbers I measured earlier say the saving is real. So why has nobody automated it?

Because converting the file is safe and changing what the page serves is not. Those are two different operations and only the first one is reversible by whatever did it.

I made the exact same measurement error again, re-checking this post

Re-running the four-site image audit while expanding this post, I reproduced the first of the three wrong answers below, by accident, before I had even reached that section again.

curl -s -H "Accept-Encoding: br,gzip" -D - -o body.html "https://example.com/"

Every one of the four sites reported zero images. Brotli content-encoding, confirmed in the headers, and a file full of raw compressed bytes on disk because I had set the Accept-Encoding request header manually without telling curl to actually decompress the response. file body.html called it “data.” The first hundred bytes were unreadable binary. Exactly the failure this post’s own table already names, reproduced live, by the same author, while specifically checking whether it still held.

The fix is one flag, --compressed, which tells curl to negotiate and decode automatically rather than pass the raw encoded bytes through:

curl -s --compressed -D - -o body.html "https://example.com/"
SiteImages (corrected)WebP / AVIF
posimyth.com180
theplusaddons.com640
nexterwp.com860
uichemy.com5151

Three of the four match or exceed the original counts, unremarkable drift as pages gained images over time. The fourth is the actual finding: uichemy.com, which had zero images in a modern format in the original audit, now serves all fifty-one of its images as WebP or AVIF. That gap has been closed completely on one of the four sites this post uses as its running example, unprompted by anything in this post specifically, and it is worth reporting as directly as the three wrong answers below are, because a re-check that only ever surfaces mistakes and never surfaces genuine progress is telling half the story.

Reversibility is the property that matters

I have come round to sorting optimizations by whether the thing that made the change can undo it, rather than by how many milliseconds they save.

Table of five optimizations sorted by reversibility. Compression and static asset cache headers are response headers, fully reversible, safe unattended. Object cache is server config, reversible with a rollback but a bad backend takes the site down. Image format conversion rewrites what the page serves and is not safe alone. Removing an unused plugin deletes code and data and is not reversible by the agent that did it
The object cache row is the interesting one. Reversible in principle, and severe enough in

practice to belong in ask-first rather than auto-run.*

OptimizationTouchesSafe unattended?Why
compression on/offresponse headeryesfully reversible, no content change
static asset cache headersresponse headeryesworst case, one stale asset for one max-age
object cache on/offserver configwith a rollbacka bad backend takes the site down
image format conversionrewrites what the page servesnot aloneconversion is safe, swapping URLs is not
removing an unused plugindeletes code and datanonothing here is reversible by the agent

This maps cleanly onto the three-tier permission model I keep returning to. Top two rows run on a schedule. Middle row proposes and waits. Bottom row does not happen without a person.

Note that saving has almost nothing to do with the ordering. The bottom row would often be the biggest win on the page.

The three wrong answers

Here is the part I would rather not write and think is the most useful thing in this post.

Table of three false findings. A page reported zero images because a brotli body was decoded as gzip. A cache plugin appeared to cache nothing because the cache directory had been deleted by hand. Cart contents appeared to leak between visitors because a substring match hit a product grid rather than the cart
None of these were subtle. All three produced an answer that looked right.
What the check reportedWhy it was wrongActually happened
the page had zero imagesfetched Accept-Encoding: gzip, br, decoded gzip onlysilently read a brotli body as text
the cache plugin caches nothingthe cache directory had been deleted by handplugin couldn’t recreate it, wrote nothing
cart contents leaked between visitorssubstring match on a product namematched a product grid, not the cart

Take the first one. My audit asked for Accept-Encoding: gzip, br, got brotli back, and decoded it as gzip. No exception, no warning, just bytes that did not parse as HTML. The result was a clean report saying four sites had zero images on them. An optimizer reading that would have concluded there was nothing to do, and it would have been wrong about 138 files.

The second one is worse in a way, because I caused it. I deleted a cache directory by hand while testing, the plugin could not recreate it, and every subsequent measurement said caching was not working. That was true of my broken rig and false of the plugin. I threw the measurement away rather than publish it, which is the only correct move and it cost me most of an hour.

The third one nearly made it into a different post. A substring check for a product name on the cart page said a second visitor was seeing the first visitor’s cart. It was a product grid block. The Store API cart for that second session was empty. Had I not checked, I would have told people their carts were leaking.

What I would put around anything that runs unattended

Four rules. Measure twice by different means. Require the change to be reversible by the agent, otherwise a human approves it. Measure again after, not just before. Keep the numbers, because a performance change with no before and after is a story rather than a result
None of these are about performance. All of them are about trusting the number that

triggered the change.*

The first rule is the cheap one and it caught two of my three errors. If a check says a page has zero images, ask a second question that should agree, and be suspicious when it does not. Zero is suspicious in a way that “fourteen” never is.

The third rule is the one people skip. Optimizers report what they did, not what changed. “Converted 138 images” is an activity log. “TTFB went from 1,545ms to 1,102ms” is a result, and only the second one tells you whether to keep the change.

The fourth rule sounds like bookkeeping and is not. Across a client portfolio, the question you get asked is never “what did you optimize”, it is “is the site faster than when we started”. Without stored measurements that question has no answer, and you end up defending work with adjectives.

Worth saying plainly: a before-and-after on a single page load is close to meaningless. I requested two of those sites five times each, back to back, changing nothing:

theplusaddons.com   549ms  795ms  273ms  415ms  476ms     (fastest to slowest: 2.9x)
uichemy.com         948ms  585ms  542ms  1011ms 792ms     (1.9x)

Same URL, same minute, nothing altered. The slowest sample is nearly three times the fastest. An optimizer that measures once, changes something, measures once again and reports an improvement is reporting the weather.

Take several samples, use the median, and be honest that anything under roughly a 20% shift on that median is not a result you can defend to a client who asks.

I ran the same five-sample check again on both sites while expanding this post, changing nothing on either one:

theplusaddons.com   270ms  487ms  479ms  444ms  441ms    (fastest to slowest: 1.8x)
uichemy.com          909ms 1433ms 1038ms  919ms 1011ms   (1.58x)

Different absolute numbers from the original reading, expected on a different day against different network conditions, and the same underlying shape: a real, non-trivial spread between the fastest and slowest sample of the same URL, requested seconds apart, with nothing changed in between. theplusaddons.com‘s spread narrowed from 2.9x to 1.8x and uichemy.com‘s from 1.9x to 1.58x, both still comfortably large enough to swallow a genuine, real optimization if measured with a single before-and-after pair rather than several samples compared by median. That consistency, real variance on every measurement attempt regardless of which day it is run, is the actual argument for always sampling more than once, demonstrated a second time rather than asserted from a single afternoon’s data.

Where this leaves automated WordPress optimization

I am not arguing against automating this work. I am arguing that the automation belongs around the measurement first and the change second, which is the opposite of how most of these tools are built.

Protuno’s performance agent, Dash, carries 16 playbooks, and the ones that run unattended are all in the top two rows of that reversibility table. The image work proposes and waits. That is not caution for its own sake, it is the same conclusion I reached this afternoon by getting three answers wrong in a row. Straight with you as on every post here: Dash is built and named but not live yet.

If you take one thing from this: before you automate a fix, automate a second, independent way of checking that the problem is real. It is less work than the fix and it is worth more.

What re-checking this specific post taught me about my own habits

Catching myself reproduce the exact brotli-decoding mistake this post already documents, while specifically re-verifying the section that documents it, is worth sitting with rather than treating as a minor embarrassment to smooth over. I knew the failure mode. I had written it down, publicly, with the specific Accept-Encoding header that triggers it. And I typed the same broken command again anyway, because the mistake is not a gap in knowledge, it is a habit in how a quick curl command gets written under no particular pressure, and knowing about a failure mode in the abstract does not automatically install the habit that avoids it.

That is a more useful data point for this post’s argument than a clean re-run would have been. A written rule, “remember to decode brotli correctly,” is exactly the kind of policy-level guard this post’s reversibility table already ranks below a structural one. The structural fix is not remembering. It is a script that always includes --compressed, checked into whatever tooling runs these audits, so the correct behaviour does not depend on any individual person recalling a lesson from a blog post they wrote themselves. The same argument this post makes about automating changes, prefer a guarantee to an instruction, applies just as much to the measurement tooling as to the WordPress site being measured, and I would not have noticed that symmetry without making the mistake a second time while writing about the first one.

The same problem at production scale

AI Agent Security: The Complete Implementation Guide (2026)

Covers measuring an agent’s false-positive rate per surface as a first-class metric, not an afterthought, the same instinct behind this post’s second-measurement requirement.

Comments