INP replaced TTI - and most teams still haven't adjusted their metrics
Interaction to Next Paint is now a Core Web Vital. What it measures, why it's harder to game than its predecessor, and the three code patterns that sink your score.
Time to Interactive was the metric teams optimized for years, and teams got very good at gaming it - lazy-loading non-critical code, deferring scripts, and shipping a fast-loading page that still janked whenever the user interacted with it. Interaction to Next Paint closes that gap. INP measures the latency of every click, tap, and keypress during the full page lifetime and reports the worst one (with some percentile smoothing). You can't make it look good while feeling bad.
What INP actually measures
INP measures the delay from when the user starts an interaction (mousedown, touchstart, keydown) to when the browser has painted the visual response. The 'next paint' part is the key distinction - the browser must actually update pixels, not just start updating. An interaction that spends 200ms in a JavaScript event handler before triggering a DOM update contributes 200ms+ to your INP even if the user never sees a spinner.
The three code patterns that sink INP
- Long synchronous event handlers - anything over 50ms in a click handler blocks the browser's paint pipeline. Break long operations into smaller tasks with scheduler.yield() or setTimeout(fn, 0).
- Synchronous state updates that trigger large re-renders - a single setState that rerenders 500 components in one pass is an INP killer. Batch updates, virtualize long lists, or defer non-visible parts of the update with startTransition.
- Third-party scripts on the main thread - analytics, chat widgets, and A/B testing scripts that execute synchronously during user interactions directly add to your INP. Audit with Chrome's Performance panel and sandbox expensive third parties in Workers where possible.
// Before: synchronous work blocks paint
button.addEventListener('click', () => {
const result = expensiveComputation(); // blocks for 300ms
updateUI(result);
});
// After: yield before the heavy work
button.addEventListener('click', async () => {
// Paint the immediate visual feedback first
showLoadingState();
await scheduler.yield(); // or: await new Promise(r => setTimeout(r, 0))
const result = expensiveComputation();
updateUI(result);
});Measuring INP in development
Chrome DevTools' Performance panel now shows INP alongside LCP and CLS in the Summary tab. For more granular data, the web-vitals library exposes `onINP()` which fires with the attribution for the worst interaction seen so far. Logging attribution data to your analytics tells you which specific element and interaction type is driving a bad score - far more actionable than a single number in Search Console.
Written by Appesto Engineering.