Core Web Vitals: How to optimize “Interaction to Next Paint” (INP)
More on the subject
There’s a lot to learn where Core Web Vitals are concerned. Let us give you a hand ✋
What is Interaction to Next Paint?
- Blocking time, during which ongoing processes on the main thread prevent the browser from responding to the user action. Part of this is input delay, which extends from when a user interacts with the page until the browser is able to start processing that interaction. If the main thread is idle, the input delay is minimal; if it’s busy, it lasts until the main thread becomes available.
- Processing time, during which the browser executes the event handlers attached to the interaction.
- Presentation time, during which the browser renders and paints the visual updates to the page.


- Green 🟩 refers to a good INP score (200 milliseconds or less).
- Yellow 🟨 means that INP needs improvement (between 200 and 500 milliseconds).
- Red 🟥 refers to a poor INP score (500 milliseconds or more).


How to Check Your INP Scores
- GSC (Google Search Console) – You can see grouped Core Web Vitals data for your own domains, together with some recommendations.
- PSI (PageSpeed Insights) – You can run a PSI report on any web page. The results page shows field data from the CrUX Report at the top of the report(this is where you can see the INP scores) and lab data from a Lighthouse test(that PSI runs in the background) at the bottom of the report.
- CrUX Dashboard – You can use the default CrUX Dashboard (doesn’t require any configuration) or build a custom one on Looker Studio to see the INP (or any other Web Vital) results of any web page.



8 best practices to optimize Interaction to Next Paint
1. Reduce unused CSS and JavaScript



While you can’t remove all unused code (e.g., if it belongs to a third-party resource, it’s used in another part of your site, or it would take too much work to remove), the overall goal is to reduce it as much as possible.
You can also use a module bundler, such as Webpack, Parcel, or Rollup, to implement code splitting so you can only load the code needed for the current page. Minifying and compressing your code files can further reduce parsing and execution time and main thread work.
2. Reduce the DOM size
- Blocking time: The browser has more work to do before it can start responding to user actions.
-
Processing time: The browser needs more time to run the callback when there are many HTML elements on the page (e.g., if your event handlers query the DOM using a method such as
querySelectorAll()). - Presentation time: It takes more time for the browser to re-paint the screen after an interaction because there are more elements to style and display.
- avoiding excessive nesting of HTML elements
- using CSS techniques, such as pseudo-elements, for styling instead of adding a new HTML element
- removing hidden elements that aren’t used (e.g., deprecated features that were hidden rather than removed, testing code, unused template fragments, etc.)
- breaking up long pages into shorter pages
- …etc.
3. Avoid rendering HTML in JavaScript
4. Analyze Common User Interactions
- Open the page you want to analyze in the Google Chrome browser in Incognito mode (which deactivates your browser extensions).
-
Open Chrome DevTools by pressing the
F12key or right-clicking the page and selecting the Inspect option. - Navigate to the Performance panel.
- Click the Record or Record and reload icon at the top of the panel to start recording.
- Start to interact with the page by performing some common user actions.
- Stop the recording when you performed all the actions you wanted.



5. Break up long-running scripts
- Look into the call tree in the Performance tab to see which functions are responsible for the long delay.
- Check if there are any code redundancies (e.g., unnecessary style calculations, repeated DOM queries, inefficient event listeners, duplicate calculations or data processing, etc.) and remove them.
-
Chunk long-running processes by using techniques such as the
setTimeout()method with small delays or breaking tasks into smaller pieces with async/await functions. - Move CPU-intensive tasks off the main thread by using Web Workers.
6. Make your scripts asynchronous
alert(), confirm(), and prompt() that stop all JavaScript execution, along with synchronous AJAX requests (which are strongly discouraged in modern web development anyway).
defer or async attribute to the <script> tag:
-
Use
asyncfor scripts that don’t depend on other scripts (e.g., third-party analytics or tracking scripts), so they can load in parallel with the creation of the DOM tree. Note, however, thatasyncscripts execute as soon as they're downloaded, which can interrupt HTML parsing. On the other hand, they can execute before the entire page is parsed, which can be useful for certain tasks such as marketing tracking or frontend performance monitoring. -
Use
deferfor scripts that depend on the DOM or need to run in a specific order, as deferred scripts execute after the browser parses the HTML and creates the DOM tree (but still before theDOMContentLoadedevent).
1 |
<!-- Load scripts asynchronously -->
|
2 |
<script async src="analytics.js"></script> |
3 |
|
4 |
<!-- Load scripts after HTML parsing but before DOMContentLoaded -->
|
5 |
<script defer src="app.js"></script> |
async and defer scripts still execute on the main thread, similar to synchronous scripts. They improve Interaction to Next Paint by not blocking rendering during download and by executing at more optimal times during page loading.
7. Minimize the impact of third-party scripts
preconnect resource hint in the following way:
1 |
<head>
|
2 |
<link rel="preconnect" href="https://stackpath.bootstrapcdn.com"> |
3 |
</head>
|
preconnect to the <head> section of the HTML document. It speeds up the download of resources from a third-party domain by performing the DNS lookup and establishing the TCP/TLS or QUIC connection before the browser would do so by default.
8. Avoid layout thrashing
offsetHeight, offsetWidth, offsetTop, getBoundingClientRect(), getComputedStyle(), etc. For a comprehensive list of JavaScript properties and methods that can cause layout thrashing, check out this list on GitHub.
1 |
/* Layout thrashing */
|
2 |
const elementWidth = element.offsetWidth; // Read |
3 |
element.style.width = elementWidth + 20 + "px"; // Write |
4 |
|
5 |
const elementHeight = element.offsetHeight; // Read (forces reflow) |
6 |
element.style.height = elementHeight + 20 + "px"; // Write |
1 |
/* Layout batching */
|
2 |
const elementWidth = element.offsetWidth; // Read |
3 |
const elementHeight = element.offsetHeight; // Read |
4 |
|
5 |
element.style.width = elementWidth + 20 + "px"; // Write |
6 |
element.style.height = elementHeight + 20 + "px"; // Write |



You can avoid layout thrashing by using a library such as FastDOM, which automatically batches read and write operations (the screenshot above was created using FastDOM’s testing page for animations).






