Optimize Interaction to Next Paint
Learn how to optimize for the Interaction to Next Paint metric.
Interaction to Next Paint (INP) is an experimental metric that assesses a page's overall responsiveness to user interactions by observing the latency of all click, tap, and keyboard interactions that occur throughout the lifespan of a user's visit to a page. The final INP value is the longest interaction observed, ignoring outliers.
To provide a good user experience, sites should strive to have an Interaction to Next Paint of 200 milliseconds or less. To ensure you're hitting this target for most of your users, a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices.
Depending on the website, there may be few to no interactions—such as pages of mostly text and images with few to no interactive elements. Or, in the case of websites such as in-browser text editors or games, there could be hundreds—even thousands—of interactions. In either case, where there's a high INP, the user experience is at risk.
It takes time and effort to improve INP, but the reward is a better user experience. In this guide, a path to improving INP will be explored.
Figure out what's causing poor INP #
Finding ways to fix a poor INP can be difficult. To start, you have to know which interactions tend to be responsible for the page's INP, out of all the interactions users have with the page. To do that, you'll need to first lean on field data, and then test in the lab to figure out what's making an interaction slow.
Find slow interactions in the field #
Field data should be the first place you go to find slow interactions. The web-vitals
JavaScript library is one way to find these interactions, thanks in large part to its ability to attribute INP values to specific elements through its attribution build:
// Use the attribution build:
import { onINP } from "web-vitals/attribution";
// Example reporting function that's passed to web-vitals:
const report = ({ name, value, attribution }) => {
console.log(name);
console.log(value);
console.log(attribution.eventType);
console.log(attribution.eventTarget);
};
// Pass the reporting function to the INP reporter:
onINP(report);
When this code runs and you interact with a page, the console may report something similar to the following:
INP
248
pointerdown
#main-nav>ul>li>button
In this case, you can see that tapping on a navigation menu toggle—which took 248 milliseconds—was responsible for the page's INP. At 248 milliseconds, this page's INP would be in the "needs improvement" category.
Once you've found elements that are responsible for slow interactions in the field, you can go into lab tools to start profiling them.
Reproduce slow interactions in the lab #
Once you have a better idea of what's responsible for slow interactions, you should profile those interactions in the lab. Fortunately, there are a couple of avenues you can pursue to reproduce problematic interactions using lab tools.
Using Chrome's performance profiler #
It's possible to surface slow interactions with the performance profiler in Chrome DevTools. To do so, take the following steps:
- Navigate to the page powering the interaction you want to test.
- Open Chrome's DevTools by pressing Ctrl+Alt+I (Cmd+Option+I on macOS).
- Go to the tab labeled Performance in DevTools.
- Click the record button at the upper left hand corner of the empty performance profiler to start recording.
- Test the desired interaction(s) on the page.
- Click the record button again to stop recording.
Once the profiler populates, you'll be able to see what work occurred to drive the interaction.
There are some cues in the profiler you can use to narrow down where the interaction occurred, notably in the interactions track situated above the main thread track:
Using Lighthouse's timespan mode #
Chrome's performance profiler—while incredibly useful and rich with diagnostic information—can be a bit intimidating to the uninitiated. If this is the case for you, Lighthouse's timespan mode might be a better alternative. To use this mode, do the following:
- Navigate to the page powering the interaction you want to test.
- Open Chrome's DevTools by pressing Ctrl+Alt+I (Cmd+Option+I on macOS).
- Go to the tab labeled Lighthouse in DevTools.
- Under the section labeled Mode, select the Timespan option.
- Select the desired device type under the section labeled Device.
- Ensure at least the checkbox labeled Performance is selected under the Categories label.
- Click the Start timespan button.
- Test the desired interaction(s) on the page.
- Click the End timespan button and wait for the audit to appear
- Once the audit populates in the Lighthouse tab, you can filter the audits by INP by clicking the INP link next to the label which reads Show audits relevant to.
At this point, you'll see a dropdown for audits that have failed or passed. When you expand that dropdown, you'll likely see a breakdown time spent during the interaction.
Regardless of the tool you use to profile slow interactions in the lab, the end result is the same in that you'll be able to take actionable information from the field and discover the culprit of slow interactions. That's information that you can take straight to your code base and figure out what you need to do to get things going faster for a better user experience.
What if you don't have field data? #
Not everyone collects field data from their website's users. While your long term performance optimization plans should include collecting field data (as that will make diagnosing issues significantly easier), if you're not doing that today, it's still possible to profile your pages and look for interactions that might be slow.
The first step is to diagnose common user flows. For example, if you're an online retailer, some common user flows would be to add items to a cart, search for products, and checkout.
When you test these user flows in the lab, consider doing the following:
- Throttle the CPU to 4x or 6x speeds to more accurately reflect interaction speeds experienced by users on low-end devices.
- Enable mobile emulation and choose a low-end mobile device. When you use mobile emulation in DevTools, the user agent string is changed to that of the emulated device, which can affect how page markup is served.
- If you have the ability, consider purchasing a cheaper Android phone (such as a Moto E) with reduced capabilities and profile interactions in the lab using remote debugging.
Once you've evaluated your potential user flows, make a note of the slowest interactions, and then you can begin to figure out where to start optimizing them. Regardless of whether you're collecting field data at this point, it depends on what part of the interaction you need to pay attention to in order to know what to optimize.
Diagnose and deal with long input delay #
The input delay is the first phase of an interaction. This is the period of time from when the user action is first received by the operating system until the browser is able to start processing the first event triggered by that input. The input delay ends right as the event callbacks for the interaction begin to run.
How to identify input delay #
Identifying input delay in Chrome's performance profiler can be done by finding the start of an interaction in the interactions panel, and then finding the beginning of when the event callbacks for that interaction start to run.
You'll always incur at least some input delay, as it takes some time for the operating system to pass the input event to the browser—but you do have some control over how long the input delay is. The key is to figure out if there is work running on the main thread that's preventing your callbacks from running.
In the previous figure a task from a third-party script is running as the user attempts to interact with the page, and therefore extends the input delay. The extended input delay affects the interaction's latency, and could therefore affect the page's INP.
How to fix long input delays #
When it comes to solutions for long input delays, it all depends. If you have expensive first-party work that could be affecting an interaction's input delay, the answer is four-fold:
- Do as little work as possible in any tasks your code kicks off.
- Break up long tasks.
- Use the Scheduler API to prioritize critical tasks and defer non-critical tasks.
- Consider using
isInputPending
to check if a user input is pending. If so, you can yield to the main thread so the event callbacks for that input can run sooner than they otherwise would.
If it's third-party code that's causing problems, then you have a lot more to consider:
- Do a complete inventory of all your website's third-party scripts.
- Figure out if more than one third-party script's functionality overlaps significantly with others.
- Cull redundant or low-value third-party scripts.
- Maintain whatever third-party scripts are left over so that they impact performance as little as possible.
This is difficult in large part because third-party JavaScript is an issue of work culture. For example tag managers make it easy for non-technical people in a company to add third-party scripts without the knowledge of the development teams. See our Best practices for tags and tag managers post on techniques for optimizing those for performance and Web Vitals.
Optimize event callbacks #
The input delay is only the first part of what INP measures. You'll also need to make sure that the event callbacks that run in response to a user interaction can complete as quickly as possible.
The best way to ensure your event callbacks run quickly is to limit what gets run to just the logic that is required to apply visual updates for the next frame. Everything else can be deferred to a subsequent task.
For example, imagine a rich text editor that formats text as your type but also updates other aspects of the UI in response to what you've written (such as word count, spelling mistakes, etc.). In addition, the application may also need to save what you've written so that if you leave and return, you haven't lost any work.
In this example, the following four things need to happen in response to characters typed by the user. However, only the first item needs to be done before the next frame is presented.
- Update the text box with what the user typed and apply any required formatting.
- Update the part of the UI that displays the current word count.
- Run logic to check for spelling mistakes.
- Save the most recent changes (either locally or to a remote database).
The code to do this might look something like this:
textBox.addEventListener('keydown', (keyboardEvent) => {
// Update the UI immediately, so the changes the user made
// are visible as soon as the next frame is presented.
updateTextBox(keyboardEvent);
// Defer all other work until at least after the next frame,
// by queuing a task in a `requestAnimationFrame()` callback.
requestAnimationFrame(() => {
setTimeout(() => {
const text = textBox.textContent;
updateWordCount(text);
checkSpelling(text);
saveChanges(text);
}, 0);
});
});
The following visualization should help explain why deferring any non-critical updates until after the next frame can reduce the processing time and thus the overall interaction latency.
While admittedly the use of setTimeout()
inside of a call to requestAnimationFrame()
in the previous code example is a bit esoteric, it is an effective and cross-browser way to ensure that the non-critical code does not block the next frame.
In the future, as new Scheduling APIs like scheduler.postTask()
and scheduler.yield()
are standardized and implemented, it will be easier for developers to write code that can be automatically prioritized without them needing to have an intimate knowledge of the browser event loop.
Minimize presentation delay #
The last step of an interaction is the presentation delay, which starts when the event callbacks have finished running, and ends when the browser is able to present the next frame to the user's display.
The unfortunate fact is that presentation delays are something you have the least amount of control over. However, here's a few things to look out for if you're noticing that frames are being delayed:
- Don't abuse
requestAnimationFrame()
for work not related to rendering.requestAnimationFrame()
callbacks are run during the rendering phase of the event loop, and must complete before the next frame can be presented. If you're usingrequestAnimationFrame()
to do work that doesn't involve changes to the user interface, understand that you could be delaying the next frame. - Be careful with
async
andawait
and your assumptions around how they work. Just because you're using these features (or Promises in general) doesn't mean that the work they do won't block rendering. It's entirely possible that anasync
function—even if it does break the work in a callback into a separate task—can still block rendering if the browser chooses to run it before the next frame is presented. This is why it's important to keep all tasks short. - Be careful with
ResizeObserver
observer callbacks. Such callbacks are run prior to rendering, and may delay presentation of the next frame if the work in them is expensive, as with event callbacks, defer any logic not needed for the very next frame.
When interactions overlap #
It's also important to understand how interactions can overlap. For example, if there are many interactions within a short period, it's possible that the processing time or presentation delay for one interaction can be a source of input delay for a subsequent interaction.
These overlaps are tricky to diagnose in the field, in part because it's difficult to track if the source of a high INP is due to multiple interactions that interfere with one another. The best thing you can do to alleviate poor responsiveness in your interactions is to do as little work as possible in your interaction's event callbacks and always keep your tasks short. This helps to reduce contention between interactions and gives the main thread a bit more breathing room.
Improving INP requires persistence #
Improving your site's INP is an iterative process. When you fix a slow interaction in the field, chances are good that—especially if your website provides tons of interactivity—you'll start to find other slow interactions, and you'll need to optimize them too.
The key to improving INP is persistence. In time, you can get your page's responsiveness in a spot where users are happy with the experience you're providing them. Chances are also good that as you develop new features for your users, you may need to go through the same process in optimizing interactions specific to them. It will take time and effort, but it's time and effort well spent.