{"id":1604,"date":"2024-04-09T08:38:19","date_gmt":"2024-04-09T14:38:19","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=1604"},"modified":"2024-04-10T08:31:27","modified_gmt":"2024-04-10T14:31:27","slug":"understanding-inp","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/understanding-inp\/","title":{"rendered":"Understanding Interaction to Next Paint (INP)"},"content":{"rendered":"\n<p><a href=\"https:\/\/developers.google.com\/search\/blog\/2023\/05\/introducing-inp\">As of March 12th 2023<\/a>, Interaction to Next Paint (INP) replaces First Input Delay (FID) as a <a href=\"https:\/\/web.dev\/articles\/vitals\">Core Web Vital<\/a> metric.<\/p>\n\n\n\n<p>FID and INP are measuring the same situation in the browser: how clunky does it feel when a user interacts with an element on the page? The good news for the web\u2014and its users\u2014is that INP provides a much better representation of real-world performance by taking every part of the interaction and rendered response into account.<\/p>\n\n\n\n<p>It\u2019s also good news for you: the steps you\u2019ve already taken to ensure a good score for FID will get you part of the way to a solid INP. Of course, no number\u2014no matter how soothingly green or alarmingly red it may be\u2014can be of any particular use without knowing exactly where they\u2019re coming from. In fact, the best way to understand the replacement is to better understand what was replaced. As is the case with so many aspects of front-end performance, the key is knowing how JavaScript makes use of the main thread. As you might imagine, every browser manages and optimizes tasks a little differently, so this article is going to oversimplify a few concepts\u2014but make no mistake, the more deeply you\u2019re able to understand <a href=\"https:\/\/www.youtube.com\/watch?v=eiC58R16hb8\">JavaScript\u2019s Event Loop<\/a>, the better equipped you\u2019ll be for handling all manner of front-end performance work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Main Thread<\/h2>\n\n\n\n<p>You might have heard JavaScript described as \u201csingle-threaded\u201d in the past, and while that\u2019s not <em>strictly<\/em> true since the advent of <a href=\"https:\/\/developer.mozilla.org\/docs\/Web\/API\/Web_Workers_API\">Web Workers<\/a>, it\u2019s still a useful way to describe JavaScript\u2019s synchronous execution model. Within a given \u201crealm\u201d\u2014like an <code>iframe<\/code>, browser tab, or web worker\u2014only one <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTML_DOM_API\/Microtask_guide\/In_depth#tasks_vs._microtasks\">task<\/a> can be executed at a time. In the context of a browser tab, this sequential execution is called the main thread, and it\u2019s shared with other browser tasks\u2014like parsing HTML, some CSS animations, and some aspects of rendering and re-rendering parts of the page.<\/p>\n\n\n\n<p>JavaScript manages \u201cexecution contexts\u201d\u2014the code currently being executed by the main thread\u2014using a data structure called the \u201ccall stack\u201d (or just \u201cthe stack\u201d). When a script starts up, the JavaScript interpreter creates a \u201cglobal context\u201d to execute the main body of the code\u2014any code that exists outside of a JavaScript function. That global context is pushed to the call stack, where it gets executed.<\/p>\n\n\n\n<p>When the interpreter encounters a function call during the execution of the global context, it pauses the global execution context, creates a \u201cfunction context\u201d (sometimes \u201clocal context\u201d) for that function call, pushes it onto the top of the stack, and executes the function. If that function call contains a function call, a new function context is created for that, pushed to the top of the stack, and executed right away. The highest context in the stack is always the current one being executed, and when it concludes, it gets popped off the stack so the next highest execution context can resume\u2014\u201clast in, first out.\u201d Eventually execution ends up back down at the global context, and either another function call is encountered and execution works its way up and back down through that and any functions that call contains, one at a time, or the global context concludes and the call stack sits empty.<\/p>\n\n\n\n<p>Now, \u201cexecute each function in the order they\u2019re encountered, one at a time\u201d were the entire story, a function that performs any kind of asynchronous task\u2014say, fetching data from a server or firing an event handler\u2019s callback function\u2014would be a performance disaster. That function execution context would either end up blocking execution until the asynchronous task completes and that task\u2019s callback function kicked off, or suddenly interrupting whatever function context the call stack happened to be working through when that task completed. So alongside the stack, JavaScript makes use of an event-driven \u201cconcurrency model\u201d made up of the \u201cevent loop\u201d and \u201ccallback queue\u201d (or \u201cmessage queue\u201d).<\/p>\n\n\n\n<p>When an asynchronous task is completed and its callback function is called, the function context for that callback function is placed in a callback queue instead of at the top of the call stack\u2014it doesn\u2019t take over execution immediately. Sitting between the callback queue and the call stack is the event loop, which is constantly polling for both the presence of function execution contexts in the callback queue and room for it in the call stack. If there\u2019s a function execution context waiting in a callback queue and the event loop determines that the call stack is sitting empty, that function execution context is pushed to the call stack and executed as though it were just called synchronously.<\/p>\n\n\n\n<p>So, for example, say we have a script that uses an old-fashioned <code>setTimeout<\/code> to log something to the console after 500 milliseconds:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">setTimeout( <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">myCallback<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n    <span class=\"hljs-built_in\">console<\/span>.log( <span class=\"hljs-string\">\"Done.\"<\/span> );\n}, <span class=\"hljs-number\">500<\/span> );\n\n<span class=\"hljs-comment\">\/\/ Output: Done.<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>First, a global context is created for the body of the script and executed. The global execution context calls the <code>setTimeout<\/code> method, so a function context for <code>setTimeout<\/code> is created at the top of the call stack, and is executed\u2014so the timer starts ticking. The <code>myCallback<\/code> function isn\u2019t added to the stack, however, since it hasn\u2019t been called yet. Since there\u2019s nothing else for the <code>setTimeout<\/code> to do, it gets popped off the stack, and the global execution context resumes. There\u2019s nothing else to do in the global context, so it pops off the stack, which is now empty.<\/p>\n\n\n\n<p>Now, at any point during this sequence of events our timer will elapse, calling <code>myCallback<\/code>. At that point, the callback function is added to a callback queue instead of being added to the stack and interrupting whatever else was being executed. Once the call stack is empty, the event loop pushes the execution context for <code>myCallback<\/code> to the stack to be executed. In this case, the main thread is done working long before the timer elapses, and our callback function is added to the empty call stack right away:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> rightNow = performance.now();\n\nsetTimeout( <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log( <span class=\"hljs-string\">`The callback function was executed after <span class=\"hljs-subst\">${ performance.now() - rightNow }<\/span> milliseconds.`<\/span> );\n}, <span class=\"hljs-number\">500<\/span>);\n\n<span class=\"hljs-comment\">\/\/ Output: The callback function was executed after 501.7000000476837 milliseconds.<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Without anything else to do on the main thread our callback fires on time, give or take a millisecond or two. But a complex JavaScript application could have tens of thousands of function contexts to power through before reaching the end of the global execution context\u2014and as fast as browsers are, these things take time. So, let\u2019s fake an overcrowded main thread by keeping the global execution context busy with a <code>while<\/code> loop that counts to a brisk five hundred million\u2014a <a href=\"https:\/\/web.dev\/articles\/optimize-long-tasks\">long task<\/a>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> rightNow = performance.now();\n<span class=\"hljs-keyword\">let<\/span> i = <span class=\"hljs-number\">0<\/span>;\n\nsetTimeout( <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">myCallback<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-built_in\">console<\/span>.log( <span class=\"hljs-string\">`The callback function was executed after <span class=\"hljs-subst\">${ performance.now() - rightNow }<\/span> milliseconds.`<\/span>);\n}, <span class=\"hljs-number\">500<\/span>);\n\n<span class=\"hljs-keyword\">while<\/span>( i &lt; <span class=\"hljs-number\">500000000<\/span> ) {\n  i++;\n}\n<span class=\"hljs-comment\">\/\/ Output: The callback function was executed after 1119.5999999996275 milliseconds.<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Once again, a global execution context is created and executed. A few lines in, it calls the <code>setTimeout<\/code> method, so a function execution context for the <code>setTimeout<\/code> is created at the top of the call stack, and the timer starts ticking. The execution context for the <code>setTimeout<\/code> is completed and popped off the stack, the global execution context resumes, and our <code>while<\/code> loop starts counting.<\/p>\n\n\n\n<p>Meanwhile, our <code>500ms<\/code> timer elapses, and <code>myCallback<\/code> is added to the callback queue\u2014but this time the call stack isn\u2019t empty when it happens, and the event loop has to wait out the rest of the global execution context before it can move <code>myCallback<\/code> over to the stack. Compared to the complex processing required to handle an entire client-rendered web page, \u201ccounting to a pretty high number\u201d isn\u2019t exactly the heaviest lift for a modern browser running on a modern laptop, but we still see a huge difference in the result: in my case, it took more than twice as long as expected for the output to show up.<\/p>\n\n\n\n<p>Now, we\u2019ve been using <code>setTimeout<\/code> for the sake of predictability, but event handlers work the same way: when the JavaScript interpreter encounters an event handler in either the global or a function context, the event becomes bound, but the callback function associated with that event listener isn\u2019t added to the call stack because that callback function hasn\u2019t been called yet\u2014not until the event fires. Once the event <em>does<\/em> fire, that callback function is added to the callback queue, just like our timer running out. So what happens if an event callback kicks in, say, while the main thread is bogged down with <a href=\"https:\/\/web.dev\/articles\/optimize-long-tasks\">long tasks<\/a> buried in the megabytes\u2019 worth of function calls required to get a JavaScript-heavy page up and running? The same thing we saw when our <code>setTimeout<\/code> elapsed: a big delay.<\/p>\n\n\n\n<p>If a user clicks on this <code>button<\/code> element right away, the callback function\u2019s execution context is created and added to the callback queue, but it can\u2019t get moved to the stack until there\u2019s room for it in the stack. A few hundred milliseconds may not seem like much on paper, but any delay between a user interaction and the result of that interaction <a href=\"https:\/\/web.dev\/articles\/rail#focus_on_the_user\">can make a <em>huge<\/em> difference in perceived performance<\/a>\u2014ask anyone that played too much Nintendo as a kid. That\u2019s First Input Delay: a measurement of the delay between the first point where a user could trigger an event handler, and the first opportunity where that event handler\u2019s callback function could be called, as the main thread has become idle. A page bogged down by parsing and executing tons of JavaScript just to get rendered and functional won\u2019t have room in the call stack for event handler callbacks to get queued up right away, meaning a longer delay between a user interaction and the callback function being invoked, and what feels like a slow, laggy page.<\/p>\n\n\n\n<p>That was First Input Delay\u2014an important metric for sure, but it wasn\u2019t telling the whole story in terms of how a user experiences a page.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is Interaction to Next Paint?<\/h2>\n\n\n\n<p>There\u2019s no question that a long delay between an event and the execution of that event handler\u2019s callback function is bad, sure\u2014but in real-world terms, \u201can opportunity for a callback function\u2019s execution context to be moved to the call stack\u201d isn\u2019t exactly the result a user is looking for when they click on a button. What really matters is the delay between the interaction and the <em>visible result<\/em> of that interaction.<\/p>\n\n\n\n<p>That\u2019s what Interaction to Next Paint sets out to measure: the delay between a user interaction and the browser\u2019s next paint\u2014the earliest opportunity to present the user with visual feedback on the results of the interaction. Of all the interactions measured during a user\u2019s time on a page, the one with the worst interaction latency is presented as the INP score\u2014after all, when it comes to tracking down and remediating performance issues, we\u2019re better off working with the bad news first.<\/p>\n\n\n\n<p>All told, there are three parts to an interaction, and all of those parts affect a page&#8217;s INP: input delay, processing time, and presentation delay.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"167\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image.webp?resize=1024%2C167&#038;ssl=1\" alt=\"Chart explaining the three parts of an interaction: Input Delay, Processing Time, and Presentation Delay. \n\nA long task blocks input delay, then there is processing time (longest bar) and presentation delay, then the Next Paint happens.\" class=\"wp-image-1642\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image-scaled.webp?resize=1024%2C167&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image-scaled.webp?resize=300%2C49&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image-scaled.webp?resize=768%2C125&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image-scaled.webp?resize=1536%2C251&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image-scaled.webp?resize=2048%2C334&amp;ssl=1 2048w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Input Delay<\/h3>\n\n\n\n<p>How long does it take for our event handlers\u2019 callback functions to find their way from the callback queue to the main thread?<\/p>\n\n\n\n<p>You know all about this one, now\u2014it\u2019s the same metric FID once captured. INP goes a lot further than FID did, though: while FID was only based on a user\u2019s first interaction, INP considers <em>all<\/em> of a user\u2019s interactions for the duration of their time on the page, in an effort to present a more accurate picture of a page\u2019s total responsiveness. INP tracks any clicks, taps, and key presses on hardware or on-screen keyboards\u2014the interactions most likely to prompt a visible change in the page.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Processing Time<\/h3>\n\n\n\n<p>How long does it take for the callback function associated with the event to run its course?<\/p>\n\n\n\n<p>Even if an event handler\u2019s callback function kicks off right away, that callback will be calling functions that call more functions, filling up the call stack and competing with any other work taking place on the main thread.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> myButton = <span class=\"hljs-built_in\">document<\/span>.querySelector( <span class=\"hljs-string\">\"button\"<\/span> );\n<span class=\"hljs-keyword\">const<\/span> rightNow = performance.now();\n\nmyButton.addEventListener( <span class=\"hljs-string\">\"click\"<\/span>, () =&gt; {\n    <span class=\"hljs-keyword\">let<\/span> i = <span class=\"hljs-number\">0<\/span>;\n    <span class=\"hljs-built_in\">console<\/span>.log( <span class=\"hljs-string\">`The button was clicked <span class=\"hljs-subst\">${ performance.now() - rightNow }<\/span> milliseconds after the page loaded.`<\/span> );\n    <span class=\"hljs-keyword\">while<\/span>( i &lt; <span class=\"hljs-number\">500000000<\/span> ) {\n        i++;\n    }\n    <span class=\"hljs-built_in\">console<\/span>.log( <span class=\"hljs-string\">`The callback function was completed <span class=\"hljs-subst\">${ performance.now() - rightNow }<\/span> milliseconds after the page loaded.`<\/span> );\n});\n\n<span class=\"hljs-comment\">\/\/ Output: The button was clicked 615.2000000001863 milliseconds after the page loaded.<\/span>\n<span class=\"hljs-comment\">\/\/ Output: The callback function was completed 927.1000000000931 milliseconds after the page loaded.<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Assuming there\u2019s nothing else bogging down the main thread and preventing this event handler\u2019s callback function, this click handler would have a great score for FID\u2014but the callback function itself contains a huge, slow task, and could take a long time to run its course and present the user with a result. A slow user experience, inaccurately summed up by a cheerful green result.<\/p>\n\n\n\n<p>Unlike FID, INP factors in these delays as well. User interactions trigger multiple events\u2014for example, a keyboard interaction will trigger <code>keydown<\/code>, <code>keyup<\/code>, and <code>keypress<\/code> events. For any given interaction, INP will capture a result for the event with the longest \u201cinteraction latency\u201d\u2014the delay between the user\u2019s interaction and the rendered response.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Presentation Delay<\/h3>\n\n\n\n<p>How quickly can rendering and compositing work take place on the main thread?<\/p>\n\n\n\n<p>Remember that the main thread doesn\u2019t just process our JavaScript, it also handles rendering. The time spent processing all the tasks created by the event handler are now competing with any number of other processes for the main thread, <em>all<\/em> of which is now competing the layout and style calculations needed to paint the results.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing Interaction to Next Paint<\/h2>\n\n\n\n<p>Now that you have a better sense what INP is measuring, it\u2019s time to start gathering data <a href=\"https:\/\/web.dev\/articles\/find-slow-interactions-in-the-field\">out in the field<\/a> and tinkering <a href=\"https:\/\/web.dev\/articles\/manually-diagnose-slow-interactions-in-the-lab\">in the lab<\/a>.<\/p>\n\n\n\n<p>For any websites included in the <a href=\"https:\/\/developer.chrome.com\/docs\/crux\/\">Chrome User Experience Report<\/a> dataset, <a href=\"https:\/\/pagespeed.web.dev\/\">PageSpeed Insights<\/a> is a great place to start getting a sense of your pages\u2019 INP. Your best bet for gathering real-world data from across a unknowable range of connection speeds, device capabilities, and user behaviors is likely to be the Chrome team\u2019s <a href=\"https:\/\/github.com\/GoogleChrome\/web-vitals\">web-vitals JavaScript library<\/a> (or a performance-focused third-party user monitoring service).<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"904\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image2.png?resize=1024%2C904&#038;ssl=1\" alt=\"Screenshot of PageSpeed Insights showing a test for frontendmasters.com, showing off all the metrics like LCP, INP, CLS, etc. All Core Web Vitals are &quot;green&quot; \/ &quot;passed&quot;\" class=\"wp-image-1613\" style=\"width:771px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image2.png?resize=1024%2C904&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image2.png?resize=300%2C265&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image2.png?resize=768%2C678&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image2.png?resize=1536%2C1355&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image2.png?w=1999&amp;ssl=1 1999w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>Then, once you\u2019ve gained a sense of your pages\u2019 biggest INP offenders from your field testing, the <a href=\"https:\/\/chromewebstore.google.com\/detail\/web-vitals\/ahfhijdlegdabablpippeagghigmibma?pli=1\">Web Vitals Chrome Extension<\/a> will allow you to test, tinker, and retest interactions in your browser\u2014not as representative as field data, but vital for getting a handle on any thorny timing issues that turned up in your field testing.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"656\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image3.png?resize=1024%2C656&#038;ssl=1\" alt=\"Screenshot of output of Web Vital Chrome Extension tester for Boost showing Largest Contentful Pain, Cumulative Layout Shift, etc.\" class=\"wp-image-1614\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image3.png?resize=1024%2C656&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image3.png?resize=300%2C192&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image3.png?resize=768%2C492&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image3.png?resize=1536%2C984&amp;ssl=1 1536w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/image3.png?w=1999&amp;ssl=1 1999w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Optimizing Interaction to Next Paint<\/h2>\n\n\n\n<p>Now that you have a better sense of how INP works behind the scenes and you\u2019re able to track down your pages\u2019 biggest INP offenders, it\u2019s time to start getting things in order. In theory, INP is a simple enough thing to optimize: get rid of those long tasks and avoid overwhelming the browser with complex layout re-calculations.<\/p>\n\n\n\n<p>Unfortunately, a simple concept doesn\u2019t translate to any quick, easy tricks in practice. Like most front-end performance work, <a href=\"https:\/\/web.dev\/explore\/how-to-optimize-inp\">optimizing Interaction to Next Paint<\/a> is a game of inches\u2014testing, tinkering, re-testing, and gradually nudging your pages toward something smaller, faster, and more respectful of your users\u2019 time and patience.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>As of March 12th 2023, Interaction to Next Paint (INP) replaces First Input Delay (FID) as a Core Web Vital metric. FID and INP are measuring the same situation in the browser: how clunky does it feel when a user interacts with an element on the page? The good news for the web\u2014and its users\u2014is [&hellip;]<\/p>\n","protected":false},"author":19,"featured_media":1620,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[148,70],"class_list":["post-1604","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-core-web-vitals","tag-performance"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/04\/interaction-thumb.jpg?fit=1000%2C500&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1604","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/19"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=1604"}],"version-history":[{"count":10,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1604\/revisions"}],"predecessor-version":[{"id":1649,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1604\/revisions\/1649"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/1620"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=1604"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=1604"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=1604"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}