The brief was simple: the client's site was slow and they knew it. Lighthouse performance score of 38, Google Search Console flagging Core Web Vitals failures, and a user drop-off rate on mobile that was making the performance problem financially visible.
Two weeks later: Lighthouse 94, all Core Web Vitals green, mobile bounce rate down 18%. Here's every change, roughly in order of impact.
Change 1: Images (Impact: ~22 Lighthouse Points)
This is almost always the highest-impact change on content sites. The client had 12MB+ of unoptimised JPEG images on the homepage. Three specific fixes:
- Format conversion: Converted all images to WebP. Modern browsers support it, the file sizes are 40-60% smaller than equivalent JPEG, and quality is indistinguishable at typical compression settings.
- Correct sizing: Images were being served at their original dimensions and scaled down in CSS. A 2400x1600 image being displayed at 400px wide. Added
srcsetattributes to serve correctly-sized images based on the viewport. - Lazy loading: Added
loading="lazy"to all below-the-fold images. Above-the-fold hero images got the opposite:fetchpriority="high"so the browser loads them first.
Change 2: Render-Blocking Resources (Impact: ~12 Points)
Three third-party scripts were loading synchronously in the <head>: an analytics script, a chat widget, and a marketing tag. Each one blocked the browser from rendering anything until it had downloaded and executed.
Fix: moved all non-critical scripts to async or defer, and deferred the chat widget entirely until user interaction (it doesn't need to be loaded until someone wants to use it). This alone dropped Time to First Byte to First Contentful Paint by about 800ms on mobile.
Change 3: Largest Contentful Paint Optimisation (Impact: ~8 Points)
The LCP element was a hero image that was being discovered by the browser late — it was set as a CSS background image rather than an HTML <img> tag. CSS background images aren't preloaded by the browser's preload scanner. Converting it to an <img> tag with fetchpriority="high" dropped LCP from 4.1s to 2.1s on a Moto G4 (the benchmark device Lighthouse uses).
Change 4: Cumulative Layout Shift (Impact: ~6 Points)
CLS was 0.18 — way above the 0.1 passing threshold. The causes were: images without width and height attributes (the browser doesn't know how much space to reserve until they load), a cookie banner that appeared after initial render and pushed all content down, and an above-the-fold ad slot without a fixed height.
All three fixed with explicit dimension declarations and a CSS reservation for the cookie banner container.
Change 5: JavaScript Bundle Size (Impact: ~10 Points)
The site was loading an entire date manipulation library (moment.js, 75KB minified) for one date formatting call. Replaced with a native Intl.DateTimeFormat call. Also found two duplicate utility functions that were loaded from different modules. Cleaned up the bundle. Total JS reduction: 89KB after compression.
The Result Timeline
Week 1 (images + render-blocking scripts): 38 → 72.
Week 2 (LCP + CLS + JS): 72 → 94.
Most performance work on content sites is concentrated in these five areas. Start with images — it's almost always the biggest win — and work down the list.