The slow part was the server, not the page

PageSpeed said AHA’s pages were slow for real users, yet 96% of the wait came before anything painted. How I told server time from page time, found a hero image that never existed, and tested every page on nine browser setups.

27 Oct 2026 4 min readCase study · Website & search programme

The problem

Pages failed Core Web Vitals for real users while lab tests passed.

The solution

Do the arithmetic first, fix what the repo controls, and test every page on every device.

96%of the LCP wait came before first paint
110 / 110pages clean in 9 browser setups
31 / 31phone interaction checks, up from 26

PageSpeed Insights said three of AHA’s pages failed Core Web Vitals on phones. My own throttled lab runs of the same pages passed comfortably. Both were right, and the gap between them was the most useful number I found.

Do the arithmetic before you optimise

PageSpeed shows real-user (field) data beside its lab test. On all three pages it told one story: first paint at 2.4 s, largest paint at 2.5 s. About 96% of the time to Largest Contentful Paint (LCP) passed before anything was drawn at all.

That rules out the usual suspects. Image weight, render-blocking CSS and image priority only matter once bytes arrive. Field Time to First Byte (TTFB) said the same: about 1.2 s waiting for the server. My lab runs, on Slow 4G with a 4× CPU throttle, measured TTFB at 36 to 108 ms. Real users were losing about a second before the race started.

Interaction delay agreed. Real-user INP was 247 to 270 ms, yet under the same throttle the page’s tap handlers took 14 ms. A page still arriving when people start tapping explains the difference.

The pages were already lean: one script, 145 to 171 KB, the main image preloaded. Hosting was outside the project’s scope. I recorded the finding as a measurement, not a proposal, so nobody commissions more page work against a problem it can’t move.

The lab sweep did find slow pages, just not these. Their causes were in the page, and fixing them took /solutions from 5,440 to 1,108 ms. That’s its own story.

The hero image that never existed

On 9 October I went through the site on my iPhone, and the homepage video seemed to load late.

The August audit had flagged that the 5.25 MB hero video had no poster frame. By October the template referenced one, and even preloaded it at high priority, but the file had never been uploaded. It returned 403. On this hosting a missing file answers 403, not 404, so it reads like a permissions error.

Two details made it worse. The lab test still passed: with no poster, it measured the headline as the LCP element, so a broken hero scored as fast. And Low Power Mode on iPhone, like data-saver modes, blocks autoplay. For those visitors the poster is the hero, permanently, and it was a blank box.

Our first guess, that the video took 20 seconds or more on mobile data, was wrong. On a throttled phone it was playing at 3.0 s, because the CDN streams it progressively.

The fix was small: point the poster and its preload at a photo that exists, verified 200. The real gap was in tooling: the build’s image guard checked each image’s hostname, never whether the file existed.

Then test every page, everywhere

Phones are AHA’s bigger audience, so I asked for every page to work on iPhone, Android and every major browser. With Claude Code I built a Playwright sweep of all 110 pages in 9 browser setups: Chrome, Edge, Firefox and Safari’s engine, on phones, a tablet and desktops. A page is clean only with no JavaScript errors, no failed requests, no sideways scroll, nothing stuck invisible, no broken images or empty sliders, one H1, and no form field under 16 px, which makes iPhone Safari zoom in.

Every setup came back 110 of 110. The pass also fixed slider drag in Firefox, where the cards are links and Firefox started its own link drag, and added CSS prefixes Safari needs.

A phone pass added interaction checks. At 320 px wide, 26 of 31 passed; after the fixes, 31 of 31 on all five phones. One fix was for a real bug: on short phones the floating call and WhatsApp buttons sat under the open menu, so a tap meant to close it could start a call.

Results (lab)

  • Homepage Lighthouse, median of 13 runs: Performance 0.85 to 0.89, Accessibility 0.96 to 1.00, failing tap targets 37 to 0.
  • Smaller copies of seven oversized photos, served through srcset, are 73% lighter.
  • Width and height on 626 of 626 images, up from 226. Lighthouse across all 74 live pages in September: median score 83, CLS 0.000.

There’s no real-user Core Web Vitals data from after these fixes yet, so these are lab figures.

Rules I kept

  1. If first paint and largest paint are close, look at the server. No image fix will move it.
  2. A passing metric can hide a broken page. Check what was measured.
  3. Check that files exist, not just where they point.
  4. Test every page on every device. Samples miss the page that breaks.

More problems, more solutions.

All posts

Non-brand clicks are the SEO number that matters

+183% non-brand clicks

A path-traced showroom in a TV browser

22 renders, path-traced offline in about 2 hours