{"id":"njzkoc7s3s2oaw6","title":"INP Subparts: Input Delay, Processing Time, and Presentation Delay","slug":"inp-subparts-debugging","summary":"INP hides three failure modes in one number. I break down input delay, processing time, and presentation delay — and show how to read each in DevTools before you ship another defer script.","imageUrl":"https://briancrabtree.me/images/journal-inp-subparts-debugging.webp","category":"Performance","date":"2026-06-15T18:00:00.000Z","featured":false,"likes":4,"author":"Brian Crabtree","content":"<h2>Why INP Alone Is Not Enough</h2>\n\n<p>Interaction to Next Paint rolls every slow tap into one millisecond score. That is useful for ranking and for comparing sites, but it is a terrible debugging number on its own. Two pages can both report 280ms INP for completely different reasons — one blocked on a busy main thread before the handler even ran, the other spending most of its budget inside a synchronous React commit.</p>\n\n<p>Google splits INP into subparts for exactly that reason. When I audit a React marketing site or a logged-in dashboard, I want to know which phase ate the budget. Fixing input delay means deferring load work. Fixing processing means shrinking handler scope. Fixing presentation delay means stopping layout thrash before the next paint. Mix them up and you ship the wrong patch.</p>\n\n<p>This post is the drill-down I reach for after the overview in <a href=\"/journal/interaction-to-next-paint-inp-guide/\">Interaction to Next Paint: What INP Measures and How I Fix It</a>. If you already have green LCP and miserable menus, start here.</p>\n\n<pre><code>// INP = input delay + processing duration + presentation delay\n// (simplified — see DevTools Interaction track for ground truth)</code></pre>\n\n<h2>The Three Subparts</h2>\n\n<p><strong>Input delay</strong> is the gap between the user event and when your handler starts. The main thread was busy — long tasks, hydration, third-party capture listeners, a hero video decode. The click queued. Users feel this as “the button ignored me.”</p>\n\n<p><strong>Processing duration</strong> is your handler and everything it synchronously triggers: state updates, DOM writes, chart redraws, analytics beacons fired inline. React 19 concurrent features can shrink this, but they do not remove a 400-node re-render you triggered on every filter keystroke.</p>\n\n<p><strong>Presentation delay</strong> is the time from handler completion until the browser paints the visual response. Forced layouts, synchronous font swaps, and animations that touch width/height instead of transform keep the screen frozen after your JavaScript “finished.”</p>\n\n<p><figure>\n  <img src=\"/images/journal-inline-inp-subparts-debugging.webp\" alt=\"Chrome Performance interaction trace showing input delay, processing duration, and presentation delay as three colored segments\" width=\"1200\" height=\"675\" loading=\"lazy\" />\n  <figcaption>Subparts tell you which phase to fix — not just that the click felt slow.</figcaption>\n</figure></p>\n\n<h2>Read It in DevTools</h2>\n\n<p>Open Chrome DevTools → Performance → record → click the interaction that feels sticky. The Interaction row expands into the three phases. Sort by duration. If input delay dominates, profile long tasks on load, not the click handler. If processing dominates, open the bottom-up flame chart on that event. If presentation delay dominates, look for Recalculate Style and Layout blocks immediately after your handler.</p>\n\n<p>PageSpeed Insights field INP appears when CrUX has enough traffic. Lab Lighthouse still approximates with Total Blocking Time and interaction traces — useful for CI, not a substitute for field subparts on production URLs. I pair PSI field INP with <a href=\"/journal/lab-vs-field-data-pagespeed/\">lab vs field data</a> so teams do not celebrate a 100 lab score while real users wait on input delay.</p>\n\n<p>When traffic is thin, I use Chrome UX Report in Search Console or local RUM. For owner-facing dashboards that segment by page and device, see <a href=\"/journal/ga4-core-web-vitals-reporting/\">GA4 and Core Web Vitals Reporting Owners Actually Read</a>.</p>\n\n<h2>Fix Patterns by Subpart</h2>\n\n<p><strong>Input delay:</strong> Split the main bundle. Defer below-fold widgets. Do not hydrate every card on the homepage before the menu works. Move Firebase, chat, and A/B snippets after first interaction or idle. Kill capture-phase listeners you did not add.</p>\n\n<p><strong>Processing duration:</strong> Defer heavy work with <code>requestAnimationFrame</code> or <code>startTransition</code>. Virtualize lists. Batch DOM reads and writes. Never run filter logic synchronously on every <code>input</code> event for a 500-row table.</p>\n\n<pre><code>button.addEventListener('click', () =&gt; {\n  requestAnimationFrame(() =&gt; {\n    startTransition(() =&gt; setOpen(true));\n  });\n});</code></pre>\n\n<p><strong>Presentation delay:</strong> Animate <code>transform</code> and <code>opacity</code>, not width. Avoid reading layout in the same turn you write it. Preload fonts with <code>font-display: optional</code> on marketing heroes so the first paint after a tap is not waiting on a swap.</p>\n\n<h2>React-Specific Traps</h2>\n\n<p>Hydration competing with the first tap is the classic marketing-site INP failure. User lands, hero paints, they hit “Services” while React is still attaching listeners and reconciling the tree — input delay spikes. I gate non-LCP chrome until after hero paint and keep the first interaction path thin.</p>\n\n<p>Third-party scripts registered in capture phase run before your bubble handler and extend input delay globally. Filter components that re-render the entire page tree on each keystroke blow processing duration. Portals and modals that measure <code>getBoundingClientRect</code> in a loop after open are presentation-delay traps.</p>\n\n<p>On mid-tier Android over USB debugging, one recorded interaction beats arguing about whether INP “should” be fine on your M-series MacBook.</p>\n\n<h2>What I Do on Client Audits</h2>\n\n<p>I pick the worst interaction on the URL — menu, primary CTA, checkout step — record it, and label which subpart won. That label goes in the report next to the PSI link. Fixes are ordered: input delay first if the thread is still choked at load, then processing, then presentation. Re-record until the Interaction track shows the phase under budget on the same device profile.</p>\n\n<p>Send the URL and the interaction that feels sticky. I will return the subpart breakdown and a three-item fix list — usually smaller than a framework rewrite and larger than compressing one JPEG.</p>","tags":["inp","core-web-vitals","performance","debugging"],"views":1}