What is the “Interaction to Next Paint” (INP) metric? In March, Google released a new Core Web Vital called Interaction to Next Paint (INP). It measures the speed of interactions with the website. In this article, you’ll learn more about INP from the developer’s perspective. I’ll guide you through testing for INP, verifying potential causes of low INP scores, and how you can leverage new React tools to optimize your website for recent changes.
It’s a scoring mechanism that analyzes latency from the time the user begins the interaction to the moment the next frame is presented with visual feedback. We’ll take a closer look at what mechanisms in modern applications cause low INP score later.
INP measures for one of the following interactions.
Clicking with a mouse Tapping on a device with a touchscreen Pressing a key on either a physical or onscreen keyboard For example, when the user types a character into the search input that filters a list of cats by their breeds, the INP value will be a duration from the moment of triggering a group of keyboard events (keydown, …, keyup) to the moment of re-rendering the updated list
VIDEO
The main goal of INP is to minimize this duration, ensuring swift visual feedback for user interactions and optimizing overall responsiveness on the page.
What INP isn't? There is already a well-known metric called “First Input Delay” (FID) which measures the time from when a user first interacts with a page to the time when the main thread is next idle. It offered developers a new way to measure responsiveness as real users experience it.
Read more: https://web.dev/articles/fid
FID vs INP - What’s the difference? Comparing these two mechanisms reveals similar measurement goal - a page responsiveness. One of the essential differences is that the FID measures only initial interaction, while the INP takes the worst latency recorded during the entire session.
💡 There's a little nuance to how the INP is calculated depending on the total interactions per website. For websites with more than 50 interactions per session, it takes the 75th percentile of the slowest latency recorded. This eliminates potential hiccups for pages with large numbers of interactions. Even though first impressions are important, the first interaction is not necessarily representative of all interactions throughout the life of a page. This is one of the reasons why INP replaces the FID.
Should you care about INP? In short, yes, if you care about SEO or user experience. The INP will be another metric that not only directly influences your website's ranking on Google search results but also provides valuable insights for tracking the responsiveness of your product.
Receiving poor results from the INP might be a sign to profile your application for performance.
Measuring INP For a released product, that runs in a production environment, accumulates user traffic, and qualifies for inclusion in the Chrome User Experience Report (CrUX ) you can quickly measure the new INP metric using PageSpeed insights . It gives a snapshot of your page and origin's performance over the previous 28 days.
Manually diagnose slow interactions
Ideally, you should rely on the field data from the CrUX report, that suggests your website struggles with slow interactions. Sometimes, however, it’s not possible, and we need another way.
The main goal is to identify the problem with lowest amount of effort at first. For manual investigation, we’ll use the Web Vitals addon . Then, dive deeper into what causes the issue. For that, we can leverage the Profiler available in the browser’s DevTools tab.
Using Web Vitals addon (Chrome)
Requirements:
Let’s start with a simple configuration of the Web Vitals addon. This will display HUD overlay as well as log information about each interaction with the page in the console.
We’ll use React Developer Tools to highlight component updates. This will help us with identifying what’s happening in our application.
💡 Once React Developer Tools is installed, open Chrome developer tools, then “Profiler” section. Enable “Highlight updates when components render”. Let’s go back to previously introduced app which allows us to filter cats by their breeds. First issue we can observe is that the list itself is long. We’re displaying over 1900 results at once. That’s already something we could optimize, but let’s leave it for later. Let’s try to interact with the page by typing “B” into the input, and then clearing it.
VIDEO
Few things are happening here:
A controlled Input component updates immediately on each character we type. A list updates on each input change. The moment of “freeze” shown on the video is the main issue that causes INP score to suffer. We can verify that by observing the INP delta times logged in the console on the right. This translates into general poor result of the INP score for our website (344 ms).
What exactly causes poor INP results? Figuring out what's causing poor INP is the most important, and the most difficult step on the road of enhancing the User Experience (UX).
As Google Chrome’s team suggest, interactions can be broken down into three phases:
The input delay It starts when the user initiates an interaction with the page, and ends when the event callbacks for the interaction begin to run. The processing time It consist of the time it makes for event callbacks to run to completion. The presentation delay It’s the time it takes for the browser to present the next frame which contains the visual result of the interaction. I found a bottleneck - now what? Optimizing your React app for performance. As a React developer you’re probably aware of core concepts used to optimize the app.
I’d distinguish following:
Memoization memo, useMemo, useCallback
Debouncing/Throttling (using a hook or a higher-order-function that triggers the callback) List virtualization List pagination Let’s go back to our simple app about cats. Take a look at the component that provides browsing functionality:
Let’s see how can we utilize already known concepts to optimize the INP time.
Issue #1 - List of cats re-renders on each change of the SearchBar component.
As we know from the React’s Reconciliation model , when state changes, React re-renders the components and all its descendants. By calling setFilterByBreed
on each SearchBar change, we perform unnecessary re-renders of the expensive CatsList
component.
VIDEO
Instead of triggering onChange immediately, let’s apply debouncing on the SearchBar component.
You can use a library, or implement a simple debounce utility by yourself. Here’s a simple higher-order-function that triggers callback
on timeout:
A SearchBar
component is a simple wrapper around mui’s TextField
component:
Using debounced onChange
:
This improves the INP, because we no longer try to re-render expensive component on each user interaction.
VIDEO
We still however, render a long list of elements in the DOM, synchronously.
Issue #2- List renders all elements at once causing low INP score by freezing the layout VIDEO
This time we could: a) Virtualize the list (by limiting amount of elements attached to the DOM tree, and displaying only such that are visible in the viewport + some offset). b) Paginate the data (combine intersection-observer with appending next elements to the DOM on user scroll).
Both solutions allow us to keep initial list lightweight, and get rid of the layout “freeze” that blocks us from interacting with the website. There’s a plenty of resources for implementing both Virtualization , and Pagination in React. You can check one of them.
VIDEO
React ≥18, concurrency, and the new mental model of reactivity Apart from what I already mentioned, there’s a relatively new approach to reactivity in React using its latest features. By introducing concurrency, we’re now able to transition (no pun intended) from well-known optimization techniques, take the responsibility from the developer, and introduce less intrusive optimizations managed by the tool itself (with a bit of our help).
Non-urgent updates
With React < 18, every update is urgent. From React 18, every update is urgent by default, but now we’re able to set a low priority on updates that could potentially block the UI.
Instead of thinking about re-renders, we should start thinking about the urgency of updates we perform.
Let’s once again go back to our unoptimized cats browser (Fig. 1).
Remember the part (Fig. 1 [8], Fig. 5 [1]) where, we update the state of the filterByBreed
on each SearchBar change? We’ll utilize the useTransition
hook to mark this as non-urgent update.
As simple as that, we were able to eliminate the “freezing” UI issue.
VIDEO
Why this works? Under the hood, react maintains a queue of updates ready to be performed. Before React 18, all updates were performed sequentially until there were no more updates left. When we update the component’s state, React will queue this update, and perform it until it completes. This however, due to the JavaScript event loop’s “Run-to-completion ” strategy, blocks processing of other tasks (that’s why we can see the UI “freeze” with non-optimized example).
With the release of React 18, a significant change introduced a new mechanism of processing the updates queue. Instead of forcefully processing an update, React now schedules a unit of update’s work, checks if the browser allows to perform it, and then completes it. Otherwise, it gives the control back to the browser. This way, long-running non-urgent updates no longer blocks the browser’s work that needs to be performed due to the higher priority.
Transition is only one of the concurrent scheduling mechanisms. Make sure to check all already-existing, and incoming hooks such as useDeferredValue , or use .
Is concurrent scheduling a solution to all problems? While the new concurrency model effectively addresses common challenges, it's not a silver bullet applicable to every aspect of your code.
Since useTransition
splits the work in chunks, for expensive-to-render components, splitting the work is impossible. That’s where we need to combine the new concurrent scheduling with already known optimization strategies.
Avoid premature optimization Understanding the optimization techniques mentioned and comprehending how the INP calculates the Web Vitals score is crucial for delivering a seamless and responsive user experience for your product.
While optimization enhances performance, it often comes with associated costs. While it's tempting to immediately implement efficiency improvements, many enhancements result in higher memory consumption and maintenance challenges .
It is essential to prioritize simplicity in our solutions. I advise vigilant monitoring of your application to identify areas causing issues and optimizing those selectively, rather than prematurely optimizing, which can introduce unnecessary complexity to your project.