Organic & AI Search

How We Took a Client Site From a Failing Google PageSpeed Score to 100/100

By Luke Zgaga · · 9 min read

Google PageSpeed Insights is one of the few tools that tells you, in a single screen, how Google experiences your website. Most sites score badly and most owners never find out why. This is the story of one we fixed in a single day — a client site that went from a failing performance score, a 4.4-second first paint and a Largest Contentful Paint that Google could not even measure, to a clean 100/100 across Performance, Accessibility, Best Practices and SEO. Here is exactly what we changed, and why each fix matters for both search rankings and paid media.

What does Google PageSpeed Insights actually measure?

PageSpeed Insights runs Google's Lighthouse audit against your page on a throttled mobile connection and reports four scores: Performance, Accessibility, Best Practices and SEO. The Performance score is the one everyone chases, and it is calculated from the Core Web Vitals and their supporting metrics: First Contentful Paint (FCP), Largest Contentful Paint (LCP), Total Blocking Time (TBT), Cumulative Layout Shift (CLS) and Speed Index. Core Web Vitals, measured on real users, feed Google's ranking systems, so page speed optimisation is technical SEO, not a vanity exercise. More on the difference between the lab score and real-user data below.

The starting point: a site Google couldn't score

Google PageSpeed Insights mobile report before optimisation, showing a failing performance score, a 4.4 second First Contentful Paint, a 4.4 second Speed Index and Largest Contentful Paint and Total Blocking Time errors
Before: no Performance score at all, a 4.4s first paint, and Google unable to detect a Largest Contentful Paint.

The "before" report tells the whole story. First Contentful Paint at 4.4 seconds on mobile (Google wants under 1.8s). Speed Index also 4.4s. And the two metrics that matter most — Largest Contentful Paint and Total Blocking Time — showing NO_LCP errors, meaning Lighthouse never saw a "largest" element paint. Because the Performance score is a weighted blend of those five metrics, with two of them missing Google simply refused to produce a number. Accessibility sat at 88, Best Practices at 96.

Fix 1: stop throwing away the server-rendered page

The site was built as a modern React application with server-side rendering, which should make it fast — the HTML arrives fully formed. But the client-side code was mounting with createRoot().render(), which discards the server-rendered DOM and rebuilds the entire page from scratch in the browser. Every visitor was paying to render the page twice. Switching to hydrateRoot(), which reuses the existing markup, was the single biggest win: First Contentful Paint dropped from 4.4s to 1.2s. If your framework supports hydration and you are not using it, this is the first thing to check.

Fix 2: make the hero image earn its place

The hero background was a 210KB JPEG. We converted it to WebP (138KB for desktop, 53KB for mobile), served the right size to the right screen with a responsive srcset, gave it explicit width and height attributes so the browser reserves space (zero layout shift), and told the browser it was the most important asset on the page with fetchpriority="high" and a <link rel="preload">. Image optimisation is the cheapest, most reliable page speed lever there is — and WebP alone typically cuts image weight by 30–60%.

Fix 3: remove render-blocking third-party resources

The page loaded an entire icon font library from a public CDN as a render-blocking stylesheet in the <head> — the browser could not paint anything until it arrived, and the CDN was occasionally rate-limiting requests, logging console errors that also dented the Best Practices score. The site only used nine of those icons. We subset the font to just those glyphs (2.2KB instead of hundreds), self-hosted it, inlined the CSS, and loaded the font with font-display: swap. One dependency, four separate audit flags cleared: render-blocking requests, font display, third-party usage and console errors.

Fix 4: browser caching and security headers

Lighthouse flagged 115KB of assets served without efficient cache lifetimes. Because the build fingerprints its JavaScript and CSS, we set them — along with images and fonts — to cache for a year as immutable, while keeping HTML on always-revalidate so deployments show instantly. In the same pass we added the trust and safety headers Lighthouse looks for: HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy and Cross-Origin-Opener-Policy. Repeat visitors now load the site almost entirely from cache.

Fix 5: the hidden LCP killer

After those changes the first paint was fast, but Lighthouse still reported no Largest Contentful Paint — on mobile and desktop. This took real diagnosis: other pages on the same site measured LCP perfectly, so it was homepage-specific. The culprit was a card carousel built with CSS scroll-snap-type: mandatory and scroll-behavior: smooth. A mandatory snap container can perform a compositor scroll at initial layout, and Chrome stops tracking LCP the moment it sees a scroll. Removing the CSS snapping (the carousel's JavaScript navigation still works) restored the metric: LCP 1.7s, TBT 40ms, and the Performance score finally appeared — at 100. The lesson: a missing LCP is not "no data", it is a bug, and it silently removes your site from a Core Web Vitals ranking signal.

Fix 6: layout stability and smooth animation

Every image without explicit dimensions is a potential layout shift, so we added width and height to all of them, including the logo and content images. The site's animated marquee was pinned to the GPU with will-change: transform so it never fights the main thread, and a chart animation that briefly produced invalid values on its first frame — the source of the remaining console errors — was corrected. CLS held at a perfect 0 throughout.

Fix 7: accessibility to 100

Accessibility is scored too, and it is often the fastest category to fix. Icon-only links gained aria-labels so screen readers can name them. Tiny 8px carousel dots became proper 24px tap targets, and footer links were padded for touch. The brand's pink accent was only 3.9:1 against white — below the WCAG AA 4.5:1 threshold for small text — so we kept the exact brand colour on headings (large text passes at 3:1) and nudged small text and button backgrounds to near-identical, compliant shades. Nobody notices the difference; the audit does.

The result

Google PageSpeed Insights mobile report after optimisation, showing 100 scores for Performance, Accessibility, Best Practices and SEO, with a 1.2 second First Contentful Paint, 1.7 second Largest Contentful Paint, 40 millisecond Total Blocking Time and zero Cumulative Layout Shift
After: 100 / 100 / 100 / 100. FCP 1.2s, LCP 1.7s, TBT 40ms, CLS 0 — every metric green.

Same site, same content, same day. Performance, Accessibility, Best Practices and SEO all at 100, with every Core Web Vital comfortably inside Google's "good" thresholds.

Why doesn't my PageSpeed score match real performance?

This is the most common question we get after a speed project, and the answer is that PageSpeed Insights shows you two different things on the same report:

  • Lab data (the 0–100 score). This is one simulated Lighthouse load: an emulated mid-range phone on a throttled mobile connection, run from a Google data centre, with an empty cache and nobody touching the page. It's repeatable, which makes it ideal for debugging, but it's one device on one connection.
  • Field data ("Discover what your real users are experiencing"). This comes from the Chrome UX Report (CrUX): real Chrome users over the previous 28 days, reported at the 75th percentile. It reflects real phones, real networks, warm caches and real interaction.

The two disagree for predictable reasons:

  • Your users aren't the emulated phone. A UK audience on recent iPhones and 5G will usually beat the lab. An audience on older Android devices and patchy 4G can do worse.
  • Caching and back/forward navigation. Returning visitors load from cache, and the lab never does.
  • Interaction. The lab can't measure Interaction to Next Paint (INP), the Core Web Vital that replaced First Input Delay in 2024. Total Blocking Time is only a stand-in for it. A page can score 100 and still feel sluggish when tapped.
  • Layout shifts after load. The lab only sees the initial load. Real users scroll, and ads, embeds or late-loading banners can shift the layout long after the Lighthouse run has finished.
  • Run-to-run noise. Lab scores move a few points between runs because of network and server variance. Don't chase single-run swings.

Which one does Google use for rankings? Field data. The Core Web Vitals Google rewards are real-user metrics, the same CrUX data behind Search Console's Core Web Vitals report. The lab score isn't a ranking signal in itself. To pass, a page needs all three Core Web Vitals in the "good" range at the 75th percentile: LCP within 2.5 seconds, INP within 200 milliseconds and CLS of 0.1 or less. Google's advice is that when you have both, field data is what you should prioritise.

Why does the field section say "No data"? CrUX only reports a URL once enough real Chrome users have visited it. New or low-traffic pages fall back to data for the whole site. If the whole site is too small, there's no field data at all, and the lab score is the best guide you have.

Why page speed matters beyond the score

Fast pages rank better, but the commercial impact runs deeper than SEO. For paid media, the landing page experience feeds Google Ads Quality Score, which directly lowers cost per click. And conversion rate is brutally sensitive to load time — Google's own research shows the probability of a bounce climbing sharply with every extra second on mobile. A faster site makes every channel you run cheaper and more effective, which is why we treat site speed as part of conversion rate optimisation, not a separate technical chore. It compounds with the work in our CRO best-practices guide.

A page speed checklist you can run today

  • Run PageSpeed Insights on mobile and note which metrics are red or missing.
  • Convert images to WebP, size them responsively, and add width and height.
  • Preload and prioritise the hero image; lazy-load everything below the fold.
  • Find render-blocking CSS and JavaScript and defer, inline or remove it.
  • Self-host fonts and icons; avoid whole libraries for a handful of glyphs.
  • Set long, immutable cache headers on fingerprinted assets.
  • If your framework server-renders, make sure the client hydrates rather than re-renders.
  • If LCP is missing, hunt for carousels, snap-scrolling or fade-in animations.
  • Fix contrast, accessible names and tap targets — Accessibility is scored too.

Common questions about Google PageSpeed optimisation

Does a PageSpeed score affect Google rankings? The score itself is not a ranking factor, but the Core Web Vitals it is built from are. Real-user field data carries more weight than the lab score, so fixing what the lab report shows is how you improve the field data.

What is a good PageSpeed score? 90 and above is "good" (green). For Core Web Vitals, Google's thresholds are LCP under 2.5s, INP under 200ms and CLS under 0.1.

Why does my report show no Largest Contentful Paint? It is almost always a page behaviour — content replaced after load, hidden by a fade-in, or a scroll triggered during load — rather than missing data. It should be treated as a bug and fixed.

Technical SEO like this sits alongside the content and authority work in our SEO & content service and the wider organic and AI search discipline — because a site that loads in a second is easier for Google, for AI answer engines and for the humans you are paying to send there. If your PageSpeed report looks like the "before" above, that is exactly the kind of problem we like fixing.

Want your site scoring 100 on PageSpeed? Let's make it happen.