Save the terrible website
LCP 7.8s. INP 840ms. CLS 0.42. 4.8MB of JavaScript. You have a budget and a mission.
- Level
- SYSTEMS
- Read time
- 15 min
- Experiment
- Available
- Type
- INTERACTIVE
The question
Given a genuinely bad page, which fixes actually move the metrics, and which ones feel productive but change nothing?
Hypothesis
A small number of structural fixes dominate. Most micro-optimisations are noise next to render-blocking resources and main-thread work.
Method
Laboratory available
The instrument for this investigation runs in the laboratory, where the controls, the live model and the observation log share one workstation.
Enter the laboratoryApply interventions one at a time. The bench models how each one affects the three Core Web Vitals and the total transfer. Order matters, some fixes are only worth applying after another one lands.
What we observed
| Metric | Good | Needs improvement | Poor |
|---|---|---|---|
| LCP. Largest Contentful Paint | ≤ 2.5s | 2.5s to 4.0s | > 4.0s |
| INP. Interaction to Next Paint | ≤ 200ms | 200ms to 500ms | > 500ms |
| CLS. Cumulative Layout Shift | ≤ 0.1 | 0.1 to 0.25 | > 0.25 |
Why it happens
What actually moves each metric
- 01LCP: remove render-blocking resources, serve the hero image in a modern format at the right size, preload it, and make sure it is not lazy-loaded.
- 02LCP: cut time to first byte, caching and edge delivery beat every client-side trick.
- 03INP: break up long tasks, move work off the main thread, and keep event handlers under a frame's budget.
- 04INP: yield with scheduler.yield() or an explicit task split so the browser can paint between chunks.
- 05CLS: reserve space, width/height on images, min-height on ad and embed slots, font-display with a matched fallback metric.
- 06CLS: never insert content above existing content after load, including banners and consent bars.
<link rel="preconnect" href="https://cdn.example.com" crossorigin><link rel="preload" as="image" href="/hero.avif" fetchpriority="high"><img src="/hero.avif" width="1600" height="900" alt="" fetchpriority="high"><!-- width/height reserve the aspect ratio box, so nothing shifts -->Bundle size matters, but not linearly. 1MB of JavaScript costs transfer time once, and parse + compile + execute time on every single load, on a CPU that may be five times slower than yours. That is why removing 200KB of JavaScript often beats removing 2MB of images.
Further research
Lab data (Lighthouse) and field data (Chrome UX Report) answer different questions. Lighthouse is a reproducible synthetic run used to compare changes; CrUX is what your actual users experienced, on their actual devices, over 28 days. A perfect Lighthouse score with a poor CrUX report means your synthetic profile does not resemble your users.
References
- 01web.devCore Web Vitals
- 02web.devOptimize INP
- 03web.devOptimize LCP