< Core Web Vitals | JavaScript />

Core Web Vitals: what is INP and how to improve it?

Eroan Boyer

March 1, 2024

38 min

INP, an acronym for “Interaction to Next Paint,” is a web performance metric introduced by Google in May 2022 to succeed another performance indicator, FID. Available in beta since February 2023 via the “Experimental” flag, it officially enters the Core Web Vitals this month, precisely on March 12, 2024.

In this article, which is entirely dedicated to it, we will explain everything you need to know about this web performance metric, and in particular how to ensure you are in the green or, at least, as close to it as possible. This is a goal you absolutely must aim for, as INP now impacts Core Web Vitals, which themselves constitute a ranking factor in Google's search results algorithm.

Google Algorithm Evolution: A Necessity

Before diving fully into measurement and INP optimization, I think it’s essential to revisit the “why.” Google and site publishers indeed had three Core Web Vitals metrics that seemed to meet their objectives, didn’t they? So why introduce, nearly five years after the kickoff, a new metric and disrupt the habits of site publishers, web agencies, and freelancers?

From Relevance to Quality Measurement

Like many site publishers, web agencies, and SEO experts, part of your monitoring likely involves closely following Google’s ranking algorithm updates. Historically, these updates primarily aimed to ensure content relevance through an increasing number of complementary mechanisms:

  • identification of low-quality text (spam in particular), duplicate content, and more recently, AI-generated content;
  • improvement of authority-related scoring through detection of “unnatural” link-building techniques: spam, link purchases, satellite site networks…

Since 2020, Google has supplemented its “Core Updates” and other “Spam Updates” with a new type of update aimed at estimating the quality of web pages and sites. With these, the American giant aims to evaluate user satisfaction levels, and not just the intrinsic relevance and popularity of content. This UX-oriented approach is, for them, the best strategy to maintain their leadership in online search.

However, evaluating and even more so measuring the User Experience is not simple: it is a vast field that, in the web, involves professions as varied as web design, security, accessibility, and front-end development. It is with this in mind that Google deployed its “Page Experience” component, which is partly based on the “Core Web Vitals” – which were once translated into French as “ Signaux Web essentiels ”. And this is what we will discuss now.

Core Web Vitals for measuring real User Experience

The “Core Web Vitals” are a set of metrics devised by Google engineers to mathematically measure the quality of the User Experience. To collect numerical data for a component that, from a user's perspective, largely involves the subconscious and emotions, they had to define the basics of what web performance actually is. This approach allowed them to identify three key components, each represented by a metric.

Starting in 2024, the Core Web Vitals will be LCP, CLS, and INP. Goodbye, FID.
As of 2024, the Core Web Vitals will be LCP, CLS, and INP. Goodbye, therefore, to FID

Perceived speed: LCP

LCP, an acronym for “Largest Contentful Paint,” aims to calculate the loading speed perceived by users. While the Speed Index would have been more relevant for this exercise, Google had to deal with the reality on the ground by opting for an indicator that is less costly to calculate. This is why LCP calculates the time it takes to display the main visible element in the viewport when a page initially loads.

In practice, the correlation between Speed Index and LCP is significant, making LCP a highly relevant metric. If only one were to be kept, it would undoubtedly be LCP.

Visual stability: CLS

CLS, an acronym for “Cumulative Layout Shift,” aims to calculate the consistency of a web interface over time. Indeed, nothing is more disruptive for a user than navigating a page where elements move chaotically. The objective is to ensure that interactions with a page result from the user's intention and not from a misclick. It is an excellent way to measure potential frustrations.

The indicator again plays its role perfectly here, providing precise insight into the technical issues of pages.

Interface responsiveness: from FID to INP

It is within the framework of this third part that we get to the heart of the matter. Since the introduction of Core Web Vitals in 2019, page responsiveness was calculated via FID, an acronym for “First Input Delay”. A particularly explicit indicator since it translates in French to “Premier retard de saisie”, and it is precisely what it measures.

However, FID was not very representative of the real user experience in terms of responsiveness, for two main reasons:

  • it only measured the first interaction, but not the subsequent ones. However, users spend an average of 90% of their time navigating a page, so the first interaction represented only a tiny fraction of what they actually experience;
  • it only measured the latency before JavaScript execution, and not the complete latency as perceived by the user. The delta could thus be significant between the two, with FID largely underestimated most of the time: the first threshold of 100 ms was rarely reached, making the metric lose its interest.

It is to correct these shortcomings that INP, an acronym for “Interaction to Next Paint”, was introduced. With this new metric, we put ourselves in the user's perspective: what we seek to obtain is a visible result on the page, and no longer the execution of a meaningless JavaScript callback function. And this throughout navigation sessions, and no longer just when pages load.

It is, at the moment when a user leaves a web page, the maximum value that is taken into account and becomes the INP from the perspective of Core Web Vitals. This approach makes sense insofar as it aims to ensure that the performance level is consistent over time.

How to measure INP?

As with other Core Web Vitals, engineers have established a precise methodology for calculating a page's INP. The goal is that regardless of the tool used, the results are consistent.

How is INP calculated?

Unlike its predecessor FID, INP can currently only be obtained through Chromium-based web browsers, namely Google Chrome, Microsoft Edge, Opera, and Samsung's mobile browser. Neither Firefox nor Safari have access to such an API. Let's now see how the metric is concretely calculated.

Three steps instead of one

As we saw earlier, INP goes further than FID in terms of measurement. During an interaction such as a click, a touch on a touchscreen, or a keyboard entry (but not scroll or hover), the metric is calculated by adding the duration of three main steps:

  1. input delay, which includes FID and calculates the latency between user input and the triggering of the corresponding event;
  2. interaction processing itself via JavaScript, CSS, or native browser functions;
  3. on-screen display of the updated page, which involves a rendering and paint phase (detailed further below).
The steps that make up INP.
The steps that make up INP. Source: https://web.dev/inp/

Since this data is time-based, the unit of measurement for INP is milliseconds. To provide a quality user experience in terms of responsiveness, Google engineers have estimated that it is necessary to be under 200 ms. The threshold is thus more generous than for FID, which makes sense as more elements are measured. Conversely, exceeding the 500 ms (half-second) threshold causes the indicator to fail.

High variability

We explained that INP measures a sum of latencies and processing times without detailing why it could take time. The answer is right before your eyes: it's your hardware, and more specifically your microprocessor's processing power and the amount of RAM it has. JavaScript execution and visual rendering are two extremely resource-intensive operations for a CPU, which executes a majority of the processing in a single thread.

Consequently, the latter quickly and regularly reaches saturation, generating the famous “Long Tasks” (over 50 ms execution time). This translates concretely into significant variability in the metrics collected from users: a desktop visitor will not have the same performance level as a mobile user. And even between smartphone users, the operating system, system settings, or the browser all constitute factors of variability.

INP breakdown based on available RAM.
Distribution of INP based on visitors' RAM: the correlation between the amount of available RAM and the metric is evident here (data from a RUMvision report).

As Google's goal is to provide metrics that are understandable and actionable by site publishers, the data is systematically segmented between mobile users on one side and desktop users on the other. A logical grouping since the experience between these two families of devices can differ greatly: screen dimensions, pixel density, input methods, attention level... We are indeed facing two totally different experiences.

75th percentile over 28 days

Given the variety of experiences, it is not feasible for most sites to aim for 100% performance, for INP as well as for other metrics. Based on this observation, Google has opted for a threshold-based approach at the 75th percentile. This means that to validate INP, at least 75% of your users must benefit from a good INP (under 200 ms, therefore).

This approach is widely accepted as it guarantees good representativeness (unlike the 50% threshold used previously) while not being as demanding as the 90th percentile threshold sometimes discussed within Google teams. Nothing is more motivating than an achievable goal.

When analyzing the data for a URL or an Origin in a report provided by Google, the 75th percentile is represented by small black pins. Each is topped with a value that corresponds to the consolidated average for all users.

Field data from CrUX in a PageSpeed Insights report
Field data from CrUX in a PageSpeed Insights report

Finally, it should be noted that for computational cost reasons, the data shared by Google is smoothed over 28 rolling days. This form of inertia makes it difficult to use data from Google's official tools to monitor performance. On the other hand, it avoids the "roller coaster" effect that would lead to massive alert dispatches at the slightest update or cache clear.

What tools can be used to measure INP?

As we've begun to see, Google collects INP via its Core Web Vitals browser API. But is it the only one doing so? And how can such data be used to manage a web project and make the right decisions? We will list the tools that answer these questions together.

The Chrome User Experience Report

We have already extensively covered the ecosystem of tools developed by Google without actually introducing it. An omission corrected now with a proper presentation of the "Chrome User Experience Report," or "CrUX" for short. The tool is a gigantic database where performance data collected from users via the famous API integrated into Chromium-based browsers (Google Chrome, Microsoft Edge) is stored.

It is in this format, within Google Search Console, that a majority of site publishers discover the INP issues affecting their pages.
It is in this form, within Google Search Console, that a majority of site publishers discover the INP issues affecting their pages.

CrUX data is what Google uses for all its reports: Core Web Vitals in Search Console, the “Discover your user experience” section in PageSpeed Insights reports… As this data is freely accessible, it is also available via APIs and BigQuery, allowing you to go far beyond what Google offers in terms of formatting and freshness.

If you want to go beyond the meager interface offered by Google in its Search Console or access recent data, we recommend using a third-party tool like Treo or Speetals. These tools can retrieve CrUX data and format it more intelligently, thus leveraging truly operational dashboards. They allow you, in particular, to filter users by geographic origin or consult the 3-year history.

Geographic performance report using CrUX field data in the Treo tool
Geographic performance report using CrUX field data in the Treo tool

Field data

The great strength of CrUX is its fully integrated and automated operation: data is collected regardless of what happens, without any solicitation from site publishers. But just because this data exists unconditionally and for free doesn't mean it should be at the core of your web performance optimization or monitoring strategy.

It is indeed possible to collect this same data via a JavaScript tag, which incidentally removes the multiple constraints inherent to CrUX. The sample size is notably larger, as an increasing number of metrics are available within browsers like Safari and Firefox (LCP in particular since version 121). Depending on the configuration adopted, “Real User Monitoring” (“RUM”) tools also allow for in-depth analysis of the collected metrics:

  • highlighting unique visits vs. multi-page visits where browser cache significantly improves performance;
  • highlighting page views that take advantage of the improved cache mechanism, or “Back Forward Cache,” which guarantees very low metrics;
  • highlighting pages with a hidden visibility state: when opened in a hidden tab, pages generally show significantly reduced performance;
  • creating relevant page groups for a given activity. Example: category pages, product pages, and SEA landing pages for an e-commerce site.

On the INP side, RUM-type tools can additionally help collect interaction data to identify the components responsible for interaction latencies. This contextualization brings significant added value because, unlike LCP and CLS, INP is only available within field data as it requires navigating through pages.

In a RUM tool like DebugBear, it is easy to identify the causes of a high INP. Here, for example, a click on the mobile menu toggle is generating high rendering times (117 ms).
In a RUM tool like DebugBear, it is easy to identify the causes of a high INP. Here, for example, a click on the mobile menu toggle is generating high rendering times (117 ms).

To integrate such a feature into your tech stack, you will be spoiled for choice. The various tools on the market all rely on the native Performance API, which allows you to retrieve user data via the JavaScript method performance.getEntries() or via a PerformanceObserver instance. In theory, it is even possible to develop your own data collection, processing, and display tool.

But the most accessible solution is to turn to a SaaS solution like Request Metrics, RUMvision, DebugBear, Dynatrace, New Relic, Raygun, or Contentsquare. Keep in mind, however, that these require the deployment of front-end JavaScript... and can therefore generate Blocking Time and contribute to an increase in your INP. So, it's up to you to decide if it's worth the effort.

Synthetic data

Synthetic data is collected by a family of tools that perform tests under so-called “laboratory” conditions. The idea is that they reproduce a user visit with a well-defined context: bandwidth and network latency, CPU power, screen dimensions and pixel density, user agent... Their main advantage is comparison, either with competing pages or over time. They are particularly perfect tools for monitoring performance and quickly identifying potential performance regressions.

When running a synthetic performance test using a tool like WebPageTest, many parameters allow for the best possible reproduction of a typical user's conditions.
When running a synthetic performance test using a tool like WebPageTest, numerous parameters allow for the best possible reproduction of typical user conditions.

As mentioned earlier, INP is not available in this type of report, and for good reason: these are robots that do not interact with pages as real users do. The solution is to turn to the “Total Blocking Time” (“TBT”) metric, which has a very strong correlation with INP. Both are indeed highly dependent on the volume of JavaScript executed.

Here too, many providers offer this type of tool, the most popular being WebPageTest, GTmetrix, Treo, or Contentsquare Speed Analysis. For a comprehensive view of performance, you should monitor both field data and synthetic data.

Your browser's developer tools

Because you are, after all, a web user like any other, nothing prevents you from using your own web browser to test the performance of your site and identify potential weaknesses in terms of INP. Google Chrome's developer tools, also known as "DevTools," have a highly advanced tool specifically designed for this: the "Performance" tab, accessible via the menu and then "More tools."

The Performance tab in Google Chrome DevTools answers the question very precisely: what happens when a user clicks on a particular element?
The Performance tab in Chrome DevTools precisely answers the question: what happens when a user clicks on a specific element?

Using it, and particularly reading its timeline and "flame graphs" (so named due to their visual appearance), allows you to identify the JavaScript code triggered after each type of interaction, as well as the repaint and reflow operations executed by the browser in its wake. Ideally, the main task should be as short as possible and the number of triggered elements as few as possible.

For technical profiles, this is the ideal place to identify THE JavaScript function that is penalizing your INP. It's also worth noting Google's recent efforts to make the tool more understandable (see the changes in Chrome 121).

Chrome 121 and later versions have significantly improved the understanding of interactions and the resulting tasks in the CPU execution thread.
Chrome 121 and later versions have significantly improved the understanding of interactions and the resulting tasks in the CPU execution thread.

We will not go into a deep dive into this tool, as understanding it requires solid front-end web development skills. Fortunately, there is illustrated documentation and, for the more motivated, even more advanced solutions for precisely profiling the "traces" generated by browsers. It is indeed possible to export them in JSON format to visualize them using tools like Perfetto UI.

The official Web Vitals browser extension

For less technical profiles, there is a "Web Vitals" extension for Google Chrome. After enabling metric logging in the console via its options, it displays in real-time all the data collected by the Web Vitals API integrated into Google Chrome. Each interaction with a page thus logs an Interaction type event, with a breakdown of the three stages that constitute it within a table.

The official Web Vitals extension for Google Chrome allows for very precise identification of a page's weaknesses. The INP is notably broken down precisely: a plus for better understanding weaknesses.
The official Web Vitals extension for Google Chrome allows for very precise identification of page weaknesses. The INP is notably broken down precisely: a plus for better understanding weaknesses

In the example above, we can see, for instance, that the 16ms of INP are linked to an " input delay " (11 ms) but also to the rendering delay (6 ms). The 16 ms threshold is actually a textbook case since on a classic computer monitor with a refresh rate of 60 Hz, a new image is generated every 16.66 ms (1000 ms / 60). As the browser cannot produce an image before the next refresh cycle, INP is in fact rarely less than 16 milliseconds.

We are now able to access our users' performance data, which will allow us to move forward by trying to understand why some users are experiencing high INP.

Is your site as fast as your visitors expect?

Discover how we can help you

What impacts INP?

JavaScript Execution

As mentioned earlier, INP is a metric that is highly sensitive to the amount of JavaScript on a page. The metric is directly correlated with the number and, more importantly, the weight of the .js files called: besides consuming bandwidth during download, a heavy script consumes significant CPU and RAM resources to execute. (remember the correlation between the INP metric and the amount of available RAM).

This is why INP is also strongly correlated with the following metrics:

  • Long Tasks: tasks that saturate CPU resource consumption for more than 50 milliseconds.
  • Blocking Time: the script execution window that effectively penalizes interactivity. If a long task lasts 87 milliseconds, the Blocking Time will be 37 ms (87 – 50 ms).
  • Long Animation Frames (LoAF): this is a new API intended to eventually replace Long Tasks, much like INP replaced FID. It offers an even more precise level of understanding of reactivity issues that can impact animations.

Third-party JavaScript, which typically weighs several tens of kilobytes, particularly impacts these metrics. If we had to rank the most impactful families, in a necessarily somewhat arbitrary way, it might look like this:

  1. A/B Tests, which by their nature modify page content early in the rendering phase.
  2. Advertising tags like Google Ads, Taboola, and ad network scripts performing real-time bidding to increase revenue.
  3. Behavioral analysis tools like Heatmap, Hotjar, and Mouseflow.
  4. Chatbots, especially conversational tools relying on AI, often developed with questionable technical expertise.
  5. Embedded videos, particularly YouTube but also Vimeo.
  6. Social snippets that, in addition to relying on JavaScript tags, load iframes that in turn call multiple scripts and resources.

As these tools are often used together and possibly called via a Tag Manager, which is also resource-intensive, they drastically increase INP, penalizing User Experience and undermining your efforts to pass Core Web Vitals. But third-party scripts aren't the only culprits. A handful of other tool families regularly integrated into themes or technical stacks can also tank INP:

  • Browser feature detection libraries like Modernizr;
  • Date manipulation tools like Moment.js;
  • Certain poorly developed polyfills;
  • ...And all JavaScript frameworks, from traditional jQuery to the very modern React and Vue.

Finally, it's worth noting that some features based on asynchronous requests (Ajax) can easily exceed the critical threshold of 500 ms. Because between a user clicking a vote button (for example) and the updated vote count appearing on screen, the browser has communicated with the server, which in turn has made a database query... And this rarely takes less than half a second.

Browser rendering

INP measures rendering latencies following user interactions. As we saw earlier, this is around 16 ms when everything goes well, with a large part related to the rendering phase steps. Each of these steps is CPU-intensive:

  1. Style: the process of identifying which CSS selectors match which parts of the DOM (e.g., .body-footer targets the site's footer...).
  2. Layout: a set of geometric calculations to position elements on a 2D plane, both absolutely within the page and relative to each other.
  3. Paint: the process of filling in the elements on the screen: text, colors, images, borders, shadows... The browser also groups these into "layers" at this time.
  4. Compositing: displaying the various "layers" based on their visual depth relative to each other. This particularly concerns elements positioned absolutely with z-indexes.
When JavaScript (or a CSS animation) triggers a visual change, the browser must perform recalculations through several costly steps.
When JavaScript (or a CSS animation) triggers a visual change, the browser must perform recalculations through several costly steps.

However, in some cases, this rendering phase can be longer even without any JavaScript involved in the process. This scenario, far more common than we'd like, is linked to the very design of websites, particularly the following aspects:

  • HTML code of pages: a complex DOM structure with many nodes and multiple successive nesting increases CPU resource consumption, and therefore the time required for layout calculations.
  • Suboptimal CSS: poorly implemented styles can generate multiple repaints and reflows on interaction. The invalidation and recalculation processes implemented by the browser then block rendering and cause INP to skyrocket.

This suboptimal rendering issue is detrimental during the initial rendering phase, obviously, but also afterwards. When a user interacts with a page and an element is modified within it, the browser will perform costly calculations to estimate if other elements on the page also need to change. It will thus go up the DOM tree to the root element, i.e., the <body> tag.

How to improve INP?

Based on these observations, we know that we need to work on optimizing JavaScript and rendering. But what does this mean concretely? This is what we will see together through examples that are unfortunately very common.

Optimize your JavaScript code

Reduce the volume of JavaScript

The first step, as logical as it is effective, is to reduce the amount of JavaScript called on pages. I'm not making this up: it's an official recommendation from the web performance gurus working on Google's teams. Common sense must be applied here, and anything that is not strictly necessary should be removed, even if it means sometimes having to arbitrate between what is necessary and what is "nice to have".

On the website of the famous newspaper Le Monde, hundreds of bytes of JavaScript assets are unused.
On the site of the famous newspaper Le Monde, hundreds of bytes of JavaScript assets are unused

Every kilobyte of JavaScript less can potentially translate into an INP improvement, with, as always, an advantage for Core Web Vitals but also for User Experience. The tool of choice to initiate this identification mission is the "Coverage" tab in the developer tools (screenshot above). It allows you to detect the usage percentage of each JavaScript file, both custom and third-party.

This process can be complex for developments relying on dependency managers like Composer or npm, which tend to increase JavaScript volume in a rather opaque way. In any case, the logic will be to use the most specific tools possible, or even custom developments, to ensure that the weight of the bundles is as low as possible.

Download and execute scripts asynchronously

The async HTML attribute, generally associated with script calls to improve performance, is a double-edged sword. On one hand, it makes calls non-blocking for rendering, which positively impacts loading time metrics like FCP, LCP, and Speed Index. But on the other hand, it makes their execution chaotic because while their download is indeed asynchronous, their execution mechanically follows immediately.

Concretely, this means that "async" type scripts can block page rendering at any time to execute. Now, if you recall, browser rendering and JavaScript execution are the two most costly operations for the browser. Chaining phases of one and then the other constitutes a catastrophic scenario in which Long Tasks, TBT, and INP are directly impacted.

The solution to ensure asynchronous behavior without blocking rendering is the defer attribute. This attribute is not only asynchronous for downloading but also ensures that scripts only execute once pages are fully rendered. However, be aware: the attribute can only be used with external calls via an HTML <script> tag. Calling Google Tag Manager with defer thus requires some reformatting:

<script src="https://www.googletagmanager.com/gtm.js?id=XXXXXX" defer fetchpriority="low"></script>
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({'gtm.start':new Date().getTime(), event:'gtm.js'});
</script>

Split long functions

Long Tasks are a major factor in increasing INP. This is notably what explains the strong correlation between the latter and TBT. However, Long Tasks generally result from monolithic JavaScript functions: since the browser can only execute code with a single CPU thread, it quickly becomes saturated and slows down the entire page.

A visualization of what happens at the interaction level when tasks are too long and the browser cannot respond quickly enough to interactions, compared to when longer tasks are divided into smaller tasks.
A visualization of what happens at the interaction level when tasks are too long and the browser cannot respond quickly enough to interactions, compared to when longer tasks are divided into smaller tasks

The solution is to break down the heaviest functions into shorter tasks to execute, ideally under 50 ms. These tasks should then be called sequentially, with the possibility between each for the browser to handle a potential user interaction or perform a screen render. Four tools can be used for this.

setTimeout(), classic but repurposed

Traditionally intended to delay the execution of a function after a defined period, setTimeout() also allows you to execute a task by decoupling it from the parent task. We use a value of zero for this, which implies an "immediate" trigger:

setTimeout(() => this._loadDelayedScripts(), 0);

The method is not the most efficient in this role, however. In particular, it does not allow prioritization to be maintained between the different subtasks chained together in this way.

requestAnimationFrame(), ideal for visual rendering

More modern and better suited to yielding constraints, requestAnimationFrame() is a JavaScript method for creating fluid and efficient animations. Its support is unfortunately still partial, but we can easily fall back to a classic setTimeout() if it is not supported by the browser.

The method, which notably allows the animation to be updated before the next screen refresh, is an ideal option for yielding functions intended to perform rendering. The latter will be automatically synchronized with the next browser refresh cycle, regardless of its display frequency. It can be used as follows:

window.requestAnimationFrame(() => {
	document.querySelector('.share-buttons-toggler').remove();
});
requestIdleCallback(), suitable for background tasks

The method is used to schedule tasks during browser idle periods, thereby de-prioritizing certain operations over others. It is very effective for executing non-urgent tasks without compromising the performance and responsiveness of the page.

requestIdleCallback() can thus be used to yield secondary functions while being certain not to block the main thread. It can be used in the following way, with, as a complement, the optimal fallback behavior to a classic setTimeout():

if ('requestIdleCallback' in window) {
 	window.requestIdleCallback(() => this._loadDelayedScripts());
} else {
	setTimeout(() => this._loadDelayedScripts(), 0);
}
scheduler.yield(), optimal but still poorly supported

This is a brand new native yielding API, currently only available in Google Chrome as an origin trial. The promise is as attractive as can be, since scheduler.yield() aims to allow the execution of JavaScript functions without them blocking rendering or user interactions.

The method relies on generating a queue, into which it adds tasks to be executed later. Its main advantage over setTimeout() is its ability to maintain the notion of execution priority. Operations not performed during a cycle are notably reintegrated at the head of the execution queue, not at the end.

There is no doubt that in a few years, this will be the main solution for unclogging an overloaded main thread, and consequently improving INP.

Reduce the number of Event Listeners

INP measures latencies related to user interactions. These can come from native features like activating a checkbox form element. But very often, they are defined in JavaScript via what are called Event Listeners. These are what trigger a JavaScript function called a "callback" after a click, a screen touch, or a keyboard entry.

The more Event Listeners there are, especially if they target generic elements like <a> or <input> tags, the worse the INP will be. With each interaction with an element associated with an Event Listener, the browser will execute the JavaScript code associated with it. Even worse, the default execution behavior of these scripts is synchronous, which increases the risk of generating Long Tasks.

Example of multiple events added to the scroll on a news site
Example of multiple events added to the scroll on a news site

Fortunately, it is possible to modify this behavior to adopt passive execution. For this, we use the "options" argument in which a passive key is defined with the value true. Be careful, however: this implementation does not allow modifying the browser's default behavior using the JavaScript preventDefault() function. Here is a code example adopting this best practice:

[].forEach.call(document.body.querySelectorAll("a[target="_blank"]"), function(el) {
    el.addEventListener(touchEvent, function(e) {
        alert("Vous quittez notre site !");
    }, {passive: true});
});

Provide feedback on interactions

We previously explained that certain features relying on Ajax could have a major negative impact on INP. In this specific case, it is not only the script execution time that impacts the metric but also, and above all, the need to communicate with the server before displaying something on the screen. From the user's perspective, this is still a waiting time, just like a classic Long Task.

The solution to avoid this type of delay is to systematically provide feedback on interactions via the UI. This is a fundamental best practice in terms of UX but unfortunately often ignored. Even before making your XHR request or executing your callback function, plan to inject an animated CSS loader or even a simple "Loading..." text.

The application areas for this recommendation are numerous:

  • Add to cart button that checks product availability in the background before validation;
  • Upvote or downvote button that increments a field in the database before displaying the updated count;
  • Load more...

Defer execution

It often happens that, even when meticulously implementing the best practices developed earlier, a page or an entire website continues to suffer from a high INP. If all the JavaScript present is actually used, there is one last resort: deferring execution. The idea is simple: rather than executing all scripts during the initial load, only execute those strictly necessary for the site's operation at first.

Other scripts, especially third-party ones, are only downloaded and executed later. By splitting execution into two distinct windows, the risk of saturating available CPU resources is greatly reduced. Several behaviors are possible here:

  • a simple delay via a setTimeOut(), usually 3 to 5 seconds after the JavaScript DOMContentLoaded event;
  • triggering on user actions like clicks, scrolls, keyboard input, etc., by adding an Event Listener (my preferred solution);
  • triggering on a scroll threshold within the page or when an area enters the active viewport, useful for example if a heavy script is only used in the footer.

Fairly simple to implement, this feature is even intuitively available for WordPress CMS users thanks to extensions like WP Rocket and Perfmatters.

Implement facades

It sometimes happens that certain very heavy scripts are only used by a minority of visitors. This is typically the case for a Chatbot, whose tooltip proudly occupies the bottom-right corner of the browser but with which only 0.5% of users interact. Or a video embed, which will mostly not be played. The question to ask here is: should we accept degrading the experience of all our visitors to meet the expectations of only 0.5% of them?

This is the problem that a "facade" addresses. Specifically, it involves reproducing the interface elements that the user is accustomed to as faithfully as possible, such as a floating bubble for a Chatbot or a cover thumbnail with a play button for a video embed. The corresponding script is only downloaded and executed when the user interacts with the trigger element, completely transparently.

Ideal for both presented cases, this solution nevertheless requires more development. HTML and CSS must be integrated to closely match the final result, while on the JavaScript side, an Event Listener must be associated with the triggers to load the script in the background when needed. Here is an example of a script used to load a Typeform form:

var typeform_loaded = false,
    typeform_load = function() {
      return new Promise(function(resolve, reject) {
        if(typeform_loaded === false) {
        const s = document.createElement('script');
        let r = false;
        s.src = "https://embed.typeform.com/next/embed.js";
        s.async = true;
        s.onerror = function(err) {
          reject(err, s);
        };
        s.onload = s.onreadystatechange = function() {
          if (!r && (!this.readyState || this.readyState == 'complete')) {
            r = true;
            typeform_loaded = true;
            resolve();
          }
        };
        const t = document.getElementsByTagName('script')[0];
        t.parentElement.insertBefore(s, t);
      }
      });
  };

 [].forEach.call(document.body.querySelectorAll(".load-typeform"), function(el) {
    el.addEventListener("mouseenter", typeform_load, {passive: true});
  });

Relying on a Service Worker or a Web Worker

When certain heavy tasks need to be executed via JavaScript, it is finally possible to opt for a different implementation. Although they address very different use cases, both Service Workers and Web Workers allow scripts to be executed asynchronously using a different CPU thread than that of the pages.

One of the most ambitious tools in this area is Partytown. By ingeniously diverting certain browser mechanisms, it makes it possible to execute third-party scripts in a Service Worker with full support for native APIs. It is notably compatible with Google Tag Manager, Google Analytics, Facebook Pixel, and Hubspot, all known for their negative impact on Blocking Time and INP.

Partytown's promise is interesting: dedicate the main thread's CPU resources to your own scripts, and execute the rest with deprioritized resources using its own CPU resources.
Partytown's promise is interesting: dedicate the main thread's CPU resources to your own scripts, and execute the rest with deprioritized resources using its own CPU resources.

This results in much better interactivity of the pages themselves. Be careful, however: Service Workers also have certain limitations, the most significant being an average latency of 300 milliseconds on initial load.

Avoid using alert()-type methods

The JavaScript methods alert(), prompt(), and confirm() are a simple and effective way to display a message or ask for user confirmation. However, they execute synchronously, blocking the page's main execution thread. In practice, this means that the interaction is considered "in progress" until the user clicks the close button.

Clicking a button that displays a native alert() window generates interactions considered very slow. Avoid using it on your site!
Clicking a button that displays a native alert() window generates interactions considered very slow. Avoid using it on your site!

This behavior can heavily penalize INP without necessarily frustrating users, so it is highly likely that a future update to Core Web Vitals will change the game. But for now, it remains a crucial point of attention for mastering INP.

Optimize your CSS

Limit the use of the universal selector

Fortunately, JavaScript isn’t systematically the sole culprit when INP spikes. It can indeed also be linked to certain technical choices by your integrator, a front-end developer, or the creator of your CMS, theme, or extension. One of the most classic errors is the overuse of the universal CSS selector, represented by an asterisk (*).

When the browser’s CSS parsing engine encounters a selector that relies partially or entirely on this selector, it has no choice but to test its relevance for all DOM nodes. An operation that is CPU-intensive, and therefore potentially generates penalizing latencies for INP. So, flee this selector like the plague by systematically favoring more precise selectors, ideally a class.

We’ll note in passing a very widely implemented exception on recent sites: the use of this selector to globally enable the CSS rendering mode border-box. This translates concretely into the presence of a CSS declaration of this type:

*,::after,::before {
 	box-sizing: inherit
}

Use performant animations

Talking about interactions without mentioning CSS animations would be a serious breach of web etiquette. Especially since, regarding INP, they can also play a non-negligible role. This is the case in two distinct scenarios, but equally penalizing:

  • continuous animations, like an animated page background, for example. They can generate high and constant resource consumption that will directly impact INP throughout users’ navigation;
  • hover animations like a zoom, a background color change, or the application of a box-shadow. They can also negatively impact INP, but often less significantly due to their occasional activation.

To avoid negatively impacting performance, the first best practice is to exclusively use animations based on transform and opacity. If you encounter width, margin, or top within an @keyframes declaration, you have reason to worry: these are problematic “non-composite” animations. This will force the browser to go through the Layout, Paint, and Compositing phases presented earlier.

This is not an arbitrary decision but the consequence of an architectural choice by browsers: opacity and transform natively benefit from hardware acceleration, and are therefore processed by the GPU and not the CPU. In concrete terms, these animations will therefore not only be much smoother visually, but they will also not generate Long Tasks.

Relieve initial rendering

Regardless of the number of scripts and CSS animations on a page, an unoptimized DOM, meaning a large and deep one, can negatively impact the INP. Because during initial rendering as well as during user interactions, the rendering engine's calculations will be more costly, and therefore longer. When possible, especially when developing a custom-built site, it is therefore essential to ensure that a minimum DOM volume is generated.

However, such an objective is generally difficult to achieve, and the thresholds of 800 and 1400 nodes, which are considered the medium and high levels not to be exceeded, are quickly reached. Fortunately, this does not mean that all is lost. There are indeed CSS properties that allow the browser to be more efficient in rendering. One of the most powerful is "Lazy Rendering".

By assigning the value auto to the CSS property content-visibility, the browser will simply ignore the portion of the page concerned to only render it once it is close to the viewport. With a major drawback, however: since the browser performs no rendering calculations, it is necessary to provide it with the final dimensions, otherwise it will generate jerky scrolling. This is done simply via the CSS property contain-intrinsic-height.

To make this clearer, here is an example of implementing Lazy Rendering on a footer. Footers are excellent candidates because they often contain more than 100 DOM nodes while being, by definition, visible quite late and by a minority of visitors.

.site-footer {
  	content-visibility: auto;
  	contain-intrinsic-height: 400px;
}

@media(min-width: 768px) {
  	.site-footer {
  	  	contain-intrinsic-height: 250px;
 	 }
}

Relieve rendering during interactions

While Lazy Rendering is relevant for improving initial load rendering times, and therefore mechanically the INP, it does not help at all in subsequent navigation sessions. To do this, there is a second native feature called "CSS containment". The latter allows limiting the browser's recalculations when an interface element changes: scrolling a slider, opening a table of contents, or simply changing a background color.

Implementing CSS containment influences how the browser traverses the DOM tree during a visual change, as previously discussed. Specifically, it involves defining, via CSS selectors, a kind of watertight barrier that will prevent the browser from going back up. With this information, it will no longer try to determine if a change within the container can impact its parents.

Since a good example is better than long explanations, here is a code snippet that allows you to isolate the sections present in a page's content area. It should be noted that for maximum impact, the CSS property contain must have the value content, but often, for compatibility reasons, we are forced to use the value layout style, which is less complete and therefore less effective.

#main-area > section {
	contain: content;
}

Reduce interactions on "dead" zones

One last CSS tool can help better control certain interaction-related behaviors, and thus impact how the INP is calculated. This is the pointer-events property which, when associated with the value none, is capable of making a DOM area non-interactive. This is a tool to be handled with care, as it can, if misused, penalize the User Experience.

Specifically, deploying pointer-events: none; on a div will systematically generate very short interactions (around 16 ms), with the drawback of making it impossible to trigger any action in JavaScript or CSS. There are few scenarios where such an implementation can truly add value.

What to do if the INP remains problematic after optimization?

If the INP remains problematic despite implementing the recommendations detailed previously, this should lead you to deeply reconsider technical choices and the front-end stack. This involves re-evaluating not only the frameworks and tools used, but also the overall architecture of the site.

To be completely explicit, improving the INP may require a complete redesign of a site. Such a decision should not be taken lightly, but it may prove essential to meet modern web performance expectations. It is crucial to select technologies and tools that not only meet current needs but are also capable of ensuring the responsiveness required by the INP.

The challenge is particularly difficult for certain JavaScript frameworks of the SPA type, even though most have made significant leaps in this area as part of their latest updates.

Screenshot of the Core Web Vitals Technology Report: percentage of sites with good INP according to the SPA framework (React, Angular, Vue.js...) from March to December 2023.
Capture of the Core Web Vitals Technology Report: percentage of sites with good INP according to the SPA framework (React, Angular, Vue.js…) from March to December 2023.

Historically, Core Web Vitals have focused on metrics that can be optimized regardless of the environment, such as LCP and CLS. This allows site publishers to adapt to these new constraints and implement a continuous improvement approach. INP, on the other hand, marks a turning point, potentially leading to a break in established practices.

It is a demanding metric that reflects the evolution of user expectations for increasingly interactive and responsive web experiences. Mastering INP is therefore not only an indicator of technical performance but also a sign of adaptation to current and future web trends.

The future of web performance

I hope this journey through the intricacies of INP has satisfied your curiosity and, perhaps, sparked your interest in the discipline. What you should take away from this is that at the heart of Google's approach lies a clear mission: to measure as accurately as possible what a user truly feels when browsing a web page. Imagine these metrics as barometers that try to capture as faithfully as possible our comfort and ease during our online explorations.

What should also be remembered is that making our users' navigation more pleasant is not just a nice initiative. It is also an excellent lever for business and reputation for a site. Whether we sell products online, offer advertising content, or anything else, ensuring that visitors navigate with ease and pleasure proves to be a strategic, judicious, and always beneficial choice.

For the skeptics as well as for those who swear only by Core Web Vitals, let's recall the real place of web performance: one of the many factors among the hundreds that play a role in Google's unfathomable ranking algorithm. The important thing to keep in mind is that it helps distinguish quality sites among a multitude, the latter always being larger and of higher quality with the increasing use of artificial intelligence and tools like ChatGPT. Web performance acts like a lighthouse, guiding users through the storm towards content that is truly worth exploring.

Now that INP is integrated into Google's Core Web Vitals, the hardest part is yet to come for many. While some have already had the opportunity to adjust, improve, and fine-tune the performance of their web pages by taking advantage of the Experimental period, others are still a long way from reaching 200 milliseconds.

Many sites display an INP in the orange (above 200ms) or even red (above 500ms).
Many sites display an INP in the orange (above 200ms) or even red (above 500ms).

Faced with this challenge, you can choose to leverage the previously detailed approaches, or entrust this project to experts like Agence Web Performance. We don't just focus on INP, but on all factors influencing performance and user experience. Our rigorous methodology and cutting-edge tools ensure that your website is not only compliant, but excels in all aspects of web performance.


Share this article now!

Continue reading