Interaction to Next Paint (INP)
The metric that measures overall page responsiveness to user interactions.
Check Your Site's Interaction to Next Paint
Enter your website URL on the homepage to see your real INP performance data.
What is INP?
Interaction to Next Paint (INP) measures the latency of all user interactions during the entire page lifecycle. It replaced First Input Delay (FID) as a Core Web Vital in March 2024. INP provides a more comprehensive view of page responsiveness by considering all interactions, not just the first one.
INP Thresholds
≤ 200ms
Good
≤ 500ms
Needs Improvement
> 500ms
Poor
Quick Wins for INP
- Add
loading="lazy"to offscreen images - Debounce input handlers with 100-300ms delay
- Use
content-visibility: autofor offscreen content - Move heavy computations to Web Workers
- Reduce third-party JavaScript
How INP is Calculated
INP observes the latency of all click, tap, and keyboard interactions during a user's visit. The final INP value is the longest interaction observed, ignoring outliers.
Interaction Types Measured
- Click/Tap - Mouse clicks and touch taps
- Keyboard - Key presses (keydown, keyup)
- Note: Scrolling and hovering are NOT included
Interaction Phases
Common Causes of Poor INP
1. Long-Running JavaScript Tasks
JavaScript that takes more than 50ms to execute blocks the main thread, preventing the browser from responding to user input.
2. Large DOM Size
Pages with thousands of DOM nodes take longer to update and render, increasing presentation delay after interactions.
3. Heavy Event Handlers
Event handlers that perform complex calculations, API calls, or large state updates block the main thread during processing.
4. Synchronous Layout Thrashing
Reading and writing to the DOM repeatedly forces the browser to recalculate styles and layout multiple times.
How to Improve INP
1. Break Up Long Tasks
- Use
setTimeoutorrequestIdleCallbackto yield - Split work into smaller chunks (< 50ms each)
- Use Web Workers for heavy computations
Example: Yielding to the main thread
function yieldToMain() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function processItems(items) {
for (const item of items) {
processItem(item);
await yieldToMain(); // Let browser handle events
}
}2. Optimize Event Handlers
- Debounce or throttle frequent events
- Move heavy work out of event handlers
- Use
requestAnimationFramefor visual updates
Example: Debouncing input
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
input.addEventListener('input', debounce(handleSearch, 300));3. Reduce DOM Complexity
- Keep DOM nodes under 1,500 elements
- Use virtualization for long lists
- Avoid deeply nested DOM structures
- Use CSS containment where possible
4. Avoid Layout Thrashing
- Batch DOM reads and writes separately
- Use
transforminstead oftop/leftfor animations - Cache layout values if reading repeatedly
Bad vs Good: Avoiding forced reflow
// Bad: Forces layout on every iteration boxes.forEach(box => { box.style.width = box.offsetWidth + 10 + 'px'; }); // Good: Batch reads, then batch writes const widths = boxes.map(box => box.offsetWidth); boxes.forEach((box, i) => { box.style.width = widths[i] + 10 + 'px'; });
Measuring INP
Using the web-vitals library
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric.value);
// Get attribution data for debugging
const attribution = metric.attribution;
console.log('Event type:', attribution.interactionType);
console.log('Target:', attribution.interactionTarget);
// Send to analytics
analytics.track('INP', {
value: metric.value,
rating: metric.rating,
interactionType: attribution.interactionType,
});
});