Core Web Vitals v2 replaces the original LCP‑FID‑CLS trio with a single user‑centric metric, Interaction to Next Paint (INP), which must stay under 200 ms for a “good” rating. Achieving that requires measuring real‑world input latency, reducing main‑thread work, and delivering paint as soon as possible after each interaction.
Step‑by‑step optimisation
1. Instrument INP – add the official Web Vitals library.
```javascript
import { getINP } from 'web-vitals';
getINP(metric => {
console.log('INP:', metric.value, metric.entries.length);
});
```
2. Audit with Lighthouse – run lighthouse --view --preset=desktop --throttling.cpuSlowdownMultiplier=4 and note the “Interaction to Next Paint” score.
3. Identify long‑task contributors – open Chrome DevTools → Performance → filter “Long Tasks”. Anything >50 ms is a candidate.
4. Split or defer heavy JavaScript – use dynamic import() for non‑critical modules, and wrap low‑priority work in requestIdleCallback or setTimeout(...,0).
5. Prioritise first‑paint CSS – inline critical CSS (e.g., via critical npm package) and mark the rest with media="print" and onload swap.
6. Reduce main‑thread blocking – enable HTTP/2 or HTTP/3, serve compressed assets (gzip/brotli), and set Cache-Control: max-age=31536000 for immutable files.
7. Validate after each change – re‑run the INP script on a real‑user device or use the Chrome User Timing API to confirm the 200 ms ceiling.
Metric thresholds
| Metric | Good | Needs Improvement | Poor |
|--------|------|-------------------|------|
| INP | ≤ 200 ms | 200 ms – 500 ms | > 500 ms |
Quick checklist
- [ ] Web Vitals library loaded on every page
- [ ] No long‑tasks > 50 ms in the critical interaction window
- [ ] Critical CSS inlined, rest async
- [ ] Heavy JS split or lazy‑loaded
- [ ] Server sends compressed, cached assets
Follow this loop until the Lighthouse INP score reports “Good” and field data shows a median INP ≤ 200 ms.