The Evolving Nature of Web Vitals
For web perf professionals, ones become familiar with Google’s Core Web Vitals. These metrics have fundamentally changed how we approach performance optimization, shifting focus from technical measurements to metrics that better reflect real user experiences. As real-world usage patterns evolve and browser capabilities advance, these metrics need to evolve too.
That’s the role Interaction to Next Paint (INP) plays. Announced earlier this year, INP is Google’s experimental metric designed to better measure a page’s overall responsiveness to user interactions. More importantly, it’s positioned to replace First Input Delay (FID) as a Core Web Vital next year, so understanding it now will give you a significant head start.
Having recently migrated several client projects to prioritize INP optimization, I’ve seen firsthand how this shift requires rethinking some of our established performance patterns.
Current Core Web Vitals
Let’s briefly review the current Core Web Vitals trio:
Largest Contentful Paint (LCP) measures loading performance by timing when the largest content element becomes visible. Good experiences should have an LCP of 2.5 seconds or less.
First Input Delay (FID) measures interactivity by timing the delay between a user’s first interaction and the browser’s response. Good experiences should have an FID of 100ms or less.
Cumulative Layout Shift (CLS) measures visual stability by quantifying unexpected layout shifts. Good experiences should have a CLS score of 0.1 or less.
These metrics have served us well, but FID in particular has shown limitations that the new INP metric aims to address.
Why FID Isn’t Good Enough
While working on a complex e-commerce project last month, our team discovered a frustrating issue: despite excellent FID scores, customers were still complaining about laggy interactions when using the product filtering system.
This highlights FID’s primary limitation: it only measures the delay before processing begins on the first interaction. It doesn’t account for:
- The processing time of the interaction itself
- How long it takes for visual feedback to appear
- Any interactions beyond the first one
In real-world usage, users care about the complete responsiveness of every interaction, not just the delay before the first one. This is precisely why Google has been developing INP.
Understanding Interaction to Next Paint (INP)
INP measures the latency of all interactions throughout a page’s lifecycle and reports a single value that represents the worst interaction experienced by the user. Unlike FID, INP encompasses the full duration from when a user interacts with the page until the next frame is painted on the screen.
An interaction typically consists of three parts:
- Input delay: Time from the interaction to when event handlers begin processing
- Processing time: Time it takes to run all event handlers
- Presentation delay: Time from completing event handlers to the next frame being rendered
INP sums these components to provide a more comprehensive picture of responsiveness. The current thresholds suggest that a good INP score should be 200ms or less, while anything above 500ms is considered poor.
How INP Is Measured in the Wild
Currently, INP data is being gathered through Chrome User Experience Report and is available in tools like PageSpeed Insights. For real-time measurement during development, you can use:
- Chrome DevTools’ Performance panel
- The web-vitals JavaScript library
- The Chrome User Experience Report
- PageSpeed Insights API
To start monitoring INP with the web-vitals library, the bare-bones:
import {onINP} from 'web-vitals';
// Measure and log INP
onINP(({value}) => {
console.log(`INP: ${value}`);
});
Chrome’s DevTools now also shows INP in the Performance panel, allowing you to identify exactly which interactions are causing issues and what’s happening during those interactions.
Optimizing for INP: Practical Strategies
After several weeks of focusing on INP optimization across different projects, we’ve identified some key strategies that consistently help improve scores:
1. Break Up Long Tasks
Long-running JavaScript tasks are the most common culprits behind poor INP scores. Break up tasks over 50ms by using techniques like:
// Instead of this
function processLargeDataSet(items) {
items.forEach(item => expensiveOperation(item));
}
// Do this
function processLargeDataSet(items) {
let i = 0;
function processChunk() {
const end = Math.min(i + 10, items.length);
for (; i < end; i++) {
expensiveOperation(items[i]);
}
if (i < items.length) {
setTimeout(processChunk, 0);
}
}
processChunk();
}
2. Optimize Event Handlers
Keep your event handlers lean. Move complex logic off the main thread when possible:
// Instead of this
button.addEventListener('click', () => {
// Complex data processing
const result = complexDataProcessing();
updateUI(result);
});
// Do this
button.addEventListener('click', () => {
// Visual feedback immediately
showLoadingIndicator();
// Move heavy processing to a Web Worker or use requestIdleCallback
setTimeout(() => {
const result = complexDataProcessing();
updateUI(result);
hideLoadingIndicator();
}, 0);
});
3. Prioritize Rendering Updates
Use requestAnimationFrame to coordinate visual updates:
button.addEventListener('click', () => {
// Do calculations
const newValues = calculateNewValues();
// Schedule rendering at the optimal time
requestAnimationFrame(() => {
updateDOMWithNewValues(newValues);
});
});
4. Implement Interaction Debouncing
For interactions like scrolling or typing that can fire frequently, implement debouncing:
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const debouncedHandler = debounce(() => {
// Handle interaction
performExpensiveOperation();
}, 150);
element.addEventListener('input', debouncedHandler);
5. Use CSS Instead of JavaScript When Possible
Offload animations and transitions to CSS, which is handled by the browser’s compositor thread:
.button {
transition: transform 0.2s ease;
}
.button:active {
transform: scale(0.95);
}
Real-world Results: A Case Study
Recently, I worked on optimizing a content-heavy site that had good LCP and CLS scores but struggled with interactions. The site’s product filtering system used complex JavaScript that ran mostly on the main thread.
We implemented the following changes:
- Moved data filtering logic to a Web Worker
- Added immediate visual feedback when filters were clicked
- Implemented progressive loading of results
- Cached filter results for common combinations
The results were dramatic:
- INP improved from 652ms to 189ms
- User engagement with filters increased by 27%
- Cart abandonment decreased by 15%
These improvements weren’t captured by FID at all, which remained relatively unchanged (and already good). This highlights why INP is such an important evolution in measuring real user experience.
Preparing for INP’s Future as a Core Web Vital
According to Google’s announcements, INP is expected to replace FID as a Core Web Vital sometime in 2024. This means it will become one of the metrics used in Google’s page experience ranking signals.
To prepare your sites:
- Start monitoring INP now using available tools
- Identify your worst-performing interactions
- Implement the optimization strategies outlined above
- Set up continuous monitoring to track improvements and regressions
The good news is that optimizing for INP generally improves the overall user experience, regardless of its status as a metric. Users appreciate responsive interfaces, so these optimizations directly benefit your users while also preparing you for the upcoming metric change.