Lighthouse: What Actually Moved the Number
Notes from tuning a static site, including the changes that measured as nothing.
Measure the median, and measure it twice
Individual Lighthouse runs on a desktop swing about ten points. Two five-run batches of effectively identical code gave median 91 and median 96 — the median itself moved five points between sittings, which means the dominant variable was what else the machine was doing.
- A single run proves nothing.
- A single batch proves little.
- Under about five points is noise.
- Only same-sitting comparisons mean anything. To measure a change, run the
old build and the new one in the same batch. Comparing against a number written down last month compares two machines.
Layout shift is where the points were
CLS went from 1.078 to 0.009, and almost all of it was one thing: a layout mode applied by JavaScript at the end of <body>, which relaid the entire document after first paint. Moving that decision into a tiny inline script in the <head>, before the first paint, fixed it.
The general rule: anything that changes layout after paint is a shift, and the biggest offenders are elements whose size is not known until something loads.
- Reserve height for anything that arrives late.
- Give every
<img>explicitwidthandheight. - Set a font
displaystrategy so text does not reflow when the webfont lands.
Reserving height honestly
A panel whose height varies by viewport got a stylesheet-stated height. The number moved four times and the direction reversed twice. Two things learned:
- Treat any reserved height as this build's measurement, never a target.
- Reserve the height of the common case, not the tallest. Reserving the tall
one leaves visible dead space every time the common case renders, which is the problem you were trying to fix.
Third-party cost is the biggest lever you may not be allowed to pull
An embedded third-party widget cost roughly nine points of median. Replacing it with a first-party equivalent that made its own data request was both faster and more capable. Analytics cost about 487ms of bootup against ~90ms for all of the site's own JavaScript — kept, because the owner wants the data. That is a legitimate answer; the point is to know the price.
Cheap wins that are actually free
preconnectto font hosts, so the handshake starts before the CSS parses.deferon scripts that are not needed for first paint.- Drop icon-font CDNs used for four glyphs. Text or inline SVG costs nothing.
- Animate
transformandopacityonly — they composite. Anything else laysout, every frame.
- Pause animation on
visibilitychange, and honourprefers-reduced-motion.
Two changes that measured as nothing
A CSS contain rule and deferring some animation to idle both landed inside the noise band. They were kept — free, correctly scoped — but they are not performance fixes and should not be cited as any. Recording that is the useful part: the next person does not spend a day re-testing them.
Authenticated pages
Lighthouse launches its own Chrome with no cookies and no localStorage, so it measures your login redirect. Log in with Puppeteer first, then hand Lighthouse the same browser with disableStorageReset: true.
The rule worth keeping
If you are about to write "this should improve performance", measure it before and after instead. Several obvious wins here measured as nothing, and one un-obvious one was worth nine points.