INP (Interaction to Next Paint) replaced FID as a Core Web Vital in March 2024 and is by far the hardest of the three to fix. Where LCP is a one-shot measurement, INP is the 98th-percentile response time across every interaction a user makes on the page - clicks, taps, key presses. One slow click can ruin the score even if everything else feels fast.
This guide walks through diagnosing slow interactions first (because you cannot fix what you cannot measure) and then works through the fixes by impact. For the underlying definition and thresholds, see the INP glossary entry.
TL;DR
INP is the 98th-percentile response time across all interactions on a page. Google's threshold is 200 ms. Every interaction has three phases: input delay (main thread busy when the input arrives), processing time (event handler running), and presentation delay (rendering the resulting paint). Fix in this order: find your actually-slow interactions with the web-vitals attribution build or DevTools, break long tasks into smaller chunks with yield patterns, defer or delay third-party scripts that block the main thread, trim heavy event handlers, and reduce DOM size when you have more than 3,000 nodes. Verify in CrUX field data because lab tools cannot reproduce real user interactions.
Key Takeaways
- ✓INP is measured across every interaction on a page, including every click after the first. One slow click can fail the metric even if the rest of the page feels fine.
- ✓Every interaction has three phases: input delay, processing time, and presentation delay. The right fix depends on which phase is dominant.
- ✓The single most common cause of poor INP is long tasks (over 50 ms) blocking the main thread when the user interacts.
- ✓Third-party scripts (analytics, tag manager, chat, A/B testing) are the second most common cause and are usually the easiest to fix.
- ✓DOM sizes above 3,000 nodes make presentation delay expensive because layout and paint have to touch more of the tree.
- ✓Framework hydration on route changes is a hidden INP killer in SPAs - it looks smooth to developers on fast machines and terrible on real mid-range Android.
What INP Measures (Briefly)
INP reports the 98th-percentile latency across every discrete interaction (click, tap, key press) a user makes during their visit. For the full definition, thresholds, and how it differs from the retired FID metric, see the Interaction to Next Paint glossary entry.
What matters for this guide: INP fails when even a small fraction of interactions are slow. Getting the average interaction fast is not enough - you have to fix the outliers.
The Three Phases of an Interaction
Every interaction has three sequential phases, and total INP for that interaction is their sum.
1. Input delay. The time between the user's action and when the event handler starts running. Input delay exists because the main thread was busy doing something else when the input arrived - parsing JavaScript, running a long task, hydrating a framework component. Common range: 20-500 ms.
2. Processing time. How long the event handler itself takes to run. A click handler that does a synchronous 200 ms calculation, opens a modal that runs a bunch of setup code, or triggers a large React re-render is spending time here. Common range: 10-800 ms.
3. Presentation delay. The time between the event handler finishing and the next frame painting the visual result. Governed by how much layout, paint, and composite work the resulting DOM change triggers. DOM size and CSS complexity dominate here. Common range: 5-400 ms.
Whichever phase is largest for your slow interactions decides your fix. Input delay problems are solved by getting the main thread out of the way. Processing time problems are solved by breaking work into smaller chunks or moving it off the critical path. Presentation delay problems are solved by simplifying the DOM and CSS the browser has to render.
Find Your Slow Interactions First
The mistake most teams make is trying to "improve INP" as a page-level goal. INP is set by specific interactions on specific components. You have to find them first.
Chrome DevTools Performance panel. Open the page, open DevTools > Performance, click record, perform the interactions you suspect are slow (open a modal, filter a list, submit a form), stop recording. In the Interactions track at the top of the trace, red bars are interactions over 200 ms. Click one and the flame chart shows exactly which script and which function ate the time.
web-vitals library with attribution. The web-vitals attribution build reports the INP number along with which element was interacted with and which of the three phases was the culprit. Instrument once, ship to production, and get real-user attribution for every slow interaction without guessing.
CrUX for the page-level number. Chrome UX Report gives you the aggregate 75th-percentile INP for the URL over the last 28 days. Use it to prioritize which pages need the local investigation.
Do not skip this step. Fixing the wrong interaction ("we optimized the mega menu") when the slow one is actually the filter dropdown will not move the score.
1. Break Long Tasks into Smaller Chunks
A long task is any main-thread task over 50 ms. While a long task is running, the browser cannot respond to user input, cannot render frames, and cannot do anything else. If a user clicks a button while a 300 ms long task is running, they wait 300 ms before their event handler even starts.
The fix is to break long work into smaller chunks and yield the main thread between them, so the browser can service any pending input in the gap.
Modern yield pattern with scheduler.yield and a setTimeout fallback:
```js
async function yieldToMain() {
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}
async function processInChunks(items) { for (const item of items) { doExpensiveWork(item); await yieldToMain(); } } ```
`scheduler.yield()` is the newest API and yields specifically to user input before returning. `setTimeout(0)` is the older fallback and still works acceptably. Do this anywhere a loop, a large data transform, or a heavy handler runs synchronously.
For the general concept of what work happens on the main thread and why it matters, see the main thread work glossary entry.
2. Defer or Delay Third-Party Scripts
Analytics, tag manager, chat widgets, A/B testing, heatmap tools, and marketing pixels are the second most common INP problem. They load on every page, run their own JavaScript on interactions, and often patch native APIs (addEventListener, fetch) so that every user action goes through their code before yours. The median page now ships 375 KB of third-party JavaScript, more than twice the first-party JavaScript on the same page, and it is exactly this code that dominates input delay on a typical site.
Use `defer` on third-party script tags so they parse after the HTML instead of blocking it. See the defer vs async glossary entry for the exact difference and when to use each.
Delay non-essential third-parties until user interaction. Analytics, heatmaps, chat widgets, and marketing pixels almost never need to run before the user interacts with the page. Delaying them until the first scroll, mousemove, or touch removes them from the initial input delay window entirely. WP Rocket, FlyingPress, and Perfmatters all offer script delay features that automate this.
Audit your tag manager. Google Tag Manager often accumulates dozens of tags that fire on page load, each of which adds to input delay. Prune aggressively; move triggers from "Page View" to specific events wherever possible.
3. Trim Heavy Event Handlers
A slow event handler is the most direct INP problem: the user clicks, your handler runs for 400 ms, INP records 400 ms of processing time.
Debounce or throttle handlers that fire rapidly. A `scroll`, `resize`, `input`, or `mousemove` handler that runs synchronously on every event will chew through the main thread. Wrap it in a debounce (fires once after activity settles) or throttle (fires at most once per interval).
Mark listeners as passive when they do not call preventDefault. `{ passive: true }` on scroll and touch listeners tells the browser it can safely start scrolling without waiting to see what the handler does. This is a large INP win on mobile.
element.addEventListener('scroll', handler, { passive: true });
Move expensive work off the main thread. For genuinely heavy work (image processing, large parsing, math), a Web Worker keeps the main thread free for interactions and paints while the work happens on another thread.
Batch DOM writes. Wrap DOM mutations in `requestAnimationFrame` so multiple changes commit in one layout pass instead of triggering layout thrash across many.
4. Reduce DOM Size
When the DOM has more than about 3,000 nodes, the presentation delay phase of every interaction gets more expensive. Every layout, style recalculation, and paint has to walk more of the tree. On mid-range mobile hardware, a 6,000-node page can spend 100+ ms in layout alone after a small DOM change.
Audit total node count. Run `document.querySelectorAll('*').length` in the console. Under 1,500 is comfortable. Between 1,500 and 3,000 is fine on desktop but tightens the budget on mobile. Above 3,000, look for the source.
Common bloat sources. Mega menus that render every submenu into the DOM instead of lazy-mounting on open. Product listings that render all 200 products instead of virtualizing. Repeated hidden sections (a modal template rendered on every page even when the modal is never opened). Deeply nested container divs added by page builders.
The fix is structural, not stylistic. Lazy-mount off-screen components, virtualize long lists, and remove wrapper divs that only exist for styling that could be done with CSS on the parent.
5. Tame Framework Hydration
In single-page applications built with React, Vue, or a similar framework, the moment the user first interacts with a fresh page is often the moment the framework is still hydrating. Hydration means attaching event handlers to the server-rendered HTML, and on a big app it can occupy the main thread for 500-2000 ms on real mobile hardware.
Break hydration into islands or use partial/progressive hydration (React Server Components, Astro islands, Qwik resumability). Only the components the user is likely to interact with hydrate on first load; the rest defer until scrolled into view or interacted with.
Split your bundle by route so the initial JavaScript parse and hydrate is only for the current page, not the entire app.
Avoid useEffect chains that trigger cascading re-renders on mount. Every re-render during hydration extends the input delay window for the user's first click.
On client-heavy SPAs, hydration is often the single biggest INP contributor and the fix is architectural rather than an optimization pass.
How to Verify the Fix
INP is even harder than LCP to verify in the lab because Lighthouse cannot reproduce real user interactions on real hardware. A Lighthouse run gives you Total Blocking Time as a proxy, but the number that Google uses for rankings is the 75th-percentile INP from real Chrome visits over the last 28 days.
Lab check (immediate). Open DevTools > Performance, throttle CPU to 4x slowdown and network to Slow 4G, and manually perform the interactions you fixed. Every interaction should complete in under 200 ms end to end. If the lab test still shows long tasks or long processing times, ship nothing.
Field verification (28-day window). Ship the fix, then watch the 75th-percentile INP in CrUX or your RUM tool. High-traffic pages show movement in one to two weeks; low-traffic pages take two to four weeks. If you shipped the web-vitals attribution build first, you can watch the specific slow interaction disappear from the attribution reports as its INP drops below the threshold.
Watch the outliers. INP is a 98th-percentile metric, so the trend that matters is the tail, not the median. A page where median INP is 60 ms and 98th is 240 ms fails; a page where median is 90 ms and 98th is 170 ms passes.
Frequently Asked Questions
What is a good INP score?
Google's thresholds are 200 ms or less for good, 200 to 500 ms for needs improvement, and above 500 ms for poor, measured at the 75th percentile of real visits over the last 28 days. To pass Core Web Vitals, 75% of your visits' worst interaction on the page has to complete in 200 ms or less.
What replaced FID?
INP replaced FID (First Input Delay) as a Core Web Vital in March 2024. FID only measured the delay before the first interaction's handler ran, which was easy to pass because most sites have plenty of idle time before the very first click. INP measures the 98th-percentile total response time across every interaction throughout the visit, which is a much harder bar and much closer to the actual user experience.
Why is INP hard to fix?
Three reasons. First, it measures every interaction, so one slow modal or one slow filter can fail the metric even when everything else feels fast. Second, the fixes are structural (break tasks up, defer third parties, trim DOM) rather than cosmetic. Third, INP is field-only - Lighthouse gives you Total Blocking Time as a proxy but cannot actually reproduce real interactions on real hardware, so verifying a fix takes real user data over the 28-day CrUX window.
Do third-party scripts affect INP?
Almost always, yes. Analytics, tag manager, chat widgets, A/B testing tools, and marketing pixels all run JavaScript on the main thread and often patch native APIs so every user action passes through their code. Deferring them (defer attribute) helps; delaying them until user interaction (with a tool like WP Rocket or FlyingPress) helps more. Auditing and pruning the tags inside Google Tag Manager is one of the highest-ROI INP wins on most sites.
How do I find which interaction is slow?
Two options. In development, use Chrome DevTools > Performance, record while you perform the interactions you suspect, and look for red bars in the Interactions track. In production, ship the web-vitals JavaScript library's attribution build, which reports the specific element interacted with and which phase (input delay, processing, or presentation) was slow. Do not try to fix INP without one of these - fixing the wrong interaction wastes weeks.