How to Fix INP Issues and Pass Core Web Vitals in 2026
Interaction to Next Paint replaced FID as a Core Web Vital. Here's how to diagnose and fix INP issues on any website.Complete methodology with examples, tools, and measurement approaches.
In March 2024, Google officially replaced First Input Delay (FID) with Interaction to Next Paint (INP) as the responsiveness metric in Core Web Vitals. The transition was announced well in advance, but the real impact became clear only after the switch: sites that passed FID with flying colors suddenly failed INP. The reason is fundamental. FID measured only the delay before the browser started processing an interaction. INP measures the entire duration from the moment a user taps, clicks, or presses a key until the browser actually renders the visual update. That means every slow event handler, every layout recalculation triggered by a DOM change, and every render-blocking paint operation now counts against you.
This guide covers exactly what INP measures, how to diagnose INP problems using real field data, and the specific code-level fixes that bring your INP below the 200-millisecond "good" threshold. No vague advice about "optimizing JavaScript." Concrete techniques with before-and-after measurements.
- INP measures the full lifecycle of an interaction: input delay + processing time + presentation delay. A 'good' INP is under 200ms. Anything above 500ms is rated 'poor' and can affect your search rankings.
- The most common INP killers are long main-thread tasks triggered by event handlers, excessive DOM size causing slow layout recalculations, and third-party scripts blocking the main thread during interactions.
- Use the Web Vitals extension, Chrome DevTools Performance panel, and CrUX data to identify which specific interactions are slow and on which pages.
- The highest-impact fixes are breaking long tasks with yield patterns, reducing DOM size, deferring non-critical third-party scripts, and virtualizing large lists.
Understanding What INP Actually Measures
INP is not a single measurement. It is a composite of three phases that happen during every user interaction. Understanding each phase is essential because the fix for a slow input delay is completely different from the fix for a slow presentation delay.
Phase 1: Input Delay
Input delay is the time between when the user interacts (click, tap, keypress) and when the browser starts running the event handler for that interaction. This delay happens when the main thread is busy doing something else at the moment the user interacts. If a third-party analytics script is executing a 150ms task when the user clicks a button, that 150ms becomes input delay because the browser cannot start processing the click until the current task finishes.
Input delay is the phase most directly affected by third-party scripts, because those scripts run tasks on the main thread that you do not control. It is also affected by your own JavaScript if you have long-running computations, complex rendering cycles, or heavy initialization logic that runs during or after page load.
Phase 2: Processing Time
Processing time is the duration of the event handler itself. When a user clicks a button and your code runs a function that updates state, fetches data, manipulates the DOM, or recalculates values, all of that execution time is processing time. This is the phase you have the most direct control over, and it is often the largest contributor to poor INP.
A common pattern that causes high processing time is synchronous state updates that trigger cascading renders. In React applications, clicking a button might update a state variable, which triggers a re-render of a large component tree, which recalculates derived values, which triggers additional renders. All of this happens synchronously during the processing phase. In vanilla JavaScript, the equivalent is direct DOM manipulation that triggers forced reflows: reading a layout property (like offsetHeight) immediately after writing a style change forces the browser to recalculate layout synchronously.
Phase 3: Presentation Delay
Presentation delay is the time from when the event handler finishes to when the browser paints the updated frame to the screen. This includes style recalculation, layout, compositing, and painting. Presentation delay is heavily influenced by DOM complexity. A page with 5,000 DOM nodes will have a faster presentation delay than a page with 50,000 DOM nodes, because the browser has more work to do when recalculating styles and layout across a larger tree.
Presentation delay also increases when event handlers trigger changes that affect large portions of the page. Toggling a CSS class on the body element, for example, can invalidate the style calculations for every element on the page. Inserting or removing elements in the middle of a long list forces the browser to recalculate layout for every subsequent element.
Source: web.dev INP documentation, Google Core Web Vitals thresholds
How INP Differs from FID (And Why Your Site Might Suddenly Fail)
FID measured only the input delay of the first interaction on a page. INP measures the input delay plus processing time plus presentation delay of every interaction throughout the entire page lifecycle, then reports the worst interaction (technically the 98th percentile to exclude outliers). This difference is enormous in practice.
A page could pass FID because the first click happened during a quiet moment on the main thread, even if every subsequent interaction was slow. INP catches those subsequent interactions. A page could pass FID because FID only measured input delay, even if the event handler took 400ms to execute. INP includes that processing time. A page could pass FID because FID used a generous 100ms threshold for "good," while INP's "good" threshold of 200ms applies to the entire interaction lifecycle (all three phases combined).
The result is that many sites that comfortably passed FID now fail INP. According to the Chrome User Experience Report, approximately 65% of origins met the "good" FID threshold before the switch, but only about 50% met the "good" INP threshold. That 15-percentage-point gap represents millions of pages that need optimization work they did not previously know about.
Diagnosing INP Issues: Finding What Is Actually Slow
Before fixing anything, you need to identify which interactions on which pages are causing poor INP. Random optimization without measurement is wasted effort. The diagnostic process has three layers: field data to identify the problem pages, lab tools to reproduce and profile specific interactions, and attribution data to pinpoint the exact cause within each interaction.
Layer 1: Field Data from CrUX and RUM
Start with the Chrome User Experience Report (CrUX) data available in Google Search Console under the Core Web Vitals report. This shows which page groups have "poor" or "needs improvement" INP scores based on real user data from Chrome. The limitation of CrUX is that it only provides page-level data, not interaction-level data. You know which pages are slow, but not which specific interactions on those pages are causing the problem.
For interaction-level attribution, you need Real User Monitoring (RUM). The web-vitals JavaScript library from Google provides an onINP callback that reports the specific element the user interacted with, the interaction type (click, keypress, etc.), and a breakdown of input delay, processing time, and presentation delay. Deploying this library with event-level logging gives you the data needed to identify exact problem interactions. Send this data to your analytics platform with the target element selector, the page URL, and the phase breakdown.
Layer 2: Lab Profiling with Chrome DevTools
Once you know which pages and interactions are slow from field data, reproduce them in Chrome DevTools. Open the Performance panel, start recording, perform the slow interaction, and stop recording. The resulting flame chart shows exactly what happened on the main thread during the interaction. Look for long tasks (blocks highlighted with red triangles) that overlap with the interaction timestamp.
Enable the "Interactions" track in the Performance panel to see INP-specific data. This track shows each interaction with its three phases color-coded. You can see immediately whether the problem is input delay (the interaction started during an existing long task), processing time (the event handler itself is slow), or presentation delay (the post-handler rendering is slow). This phase attribution is critical because the fix differs for each phase.
Layer 3: Long Animation Frames API
The Long Animation Frames (LoAF) API provides programmatic access to detailed frame timing data including script attribution. Unlike the Performance panel which requires manual profiling, LoAF runs continuously and can be sampled in production. Each long animation frame entry includes the scripts that executed during the frame, their source URLs, and their execution duration. This lets you identify third-party scripts that contribute to slow interactions without needing to reproduce the issue locally.
INP Diagnostic Workflow
Use CrUX data in Search Console to find pages with poor INP. Sort by traffic to prioritize high-impact pages. Export the list of URLs that need investigation.
Deploy the web-vitals library with interaction attribution. Collect data for 48-72 hours. Analyze which specific interactions (button clicks, form inputs, menu toggles) have the highest INP values on each problem page.
Open each problem page in Chrome DevTools Performance panel. Reproduce the slow interaction while recording. Examine the flame chart to determine whether the bottleneck is input delay, processing time, or presentation delay.
For input delay: identify which long task is blocking the main thread. For processing time: identify which functions in the event handler are slowest. For presentation delay: check DOM size and layout thrashing. Use LoAF API data for third-party attribution.
Fix 1: Breaking Long Tasks with Yielding Patterns
The single highest-impact technique for improving INP is breaking long tasks into smaller chunks that yield back to the main thread between chunks. A "long task" is any task that blocks the main thread for more than 50ms. During a long task, the browser cannot respond to user input, which directly increases input delay for any interaction that occurs during the task.
The scheduler.yield() Pattern
The scheduler.yield() API (available in Chrome 129+) is the cleanest way to yield during a long task. Unlike setTimeout(0) or requestAnimationFrame, scheduler.yield() preserves task priority and resumes execution at the front of the task queue rather than the back. This means yielding does not cause your code to be starved by lower-priority work.
The pattern is straightforward: identify the long task in your event handler, break it into logical chunks, and insert a yield point between chunks. For example, if a click handler updates state, recalculates a derived list, and then updates the DOM, you can yield between each of those three operations. The browser gets an opportunity to process any pending user input between chunks, which reduces input delay for concurrent interactions.
For browsers that do not yet support scheduler.yield(), the fallback is wrapping chunks in setTimeout(resolve, 0) inside a Promise. This yields to the main thread but puts your continuation at the back of the task queue. The practical difference is usually negligible unless the page has many competing tasks.
Where to Yield in React Applications
In React applications, the most impactful yield points are in event handlers that trigger expensive state updates. React 18+ concurrent features (startTransition, useDeferredValue) provide built-in mechanisms for yielding during renders. Wrapping non-urgent state updates in startTransition tells React that the update can be interrupted by higher-priority work, including user interactions. This is the React-idiomatic way to improve INP.
For example, if clicking a filter button updates both a selected filter indicator (urgent, should render immediately) and a filtered results list (can be deferred), wrap the results list update in startTransition. The filter indicator updates immediately, giving the user instant visual feedback, while the results list re-renders in the background without blocking subsequent interactions.
Fix 2: Reducing DOM Size and Layout Complexity
DOM size directly impacts presentation delay. Every time the browser needs to recalculate styles or layout after a DOM change, the cost scales with the number of elements that could be affected. Google recommends keeping total DOM size below 1,400 elements, with a maximum depth of 32 levels and a maximum of 60 child elements per node. In practice, many modern web applications far exceed these guidelines, especially SPAs that render complex dashboards, tables, or feeds.
Virtualization for Large Lists and Tables
If your page renders a list or table with more than 50-100 items, virtualization is the most effective technique for reducing DOM size. Virtualization renders only the items currently visible in the viewport (plus a small overscan buffer), removing items from the DOM as they scroll out of view. A list of 10,000 items might have only 20-30 elements in the DOM at any given time.
Libraries like TanStack Virtual, react-window, and react-virtuoso handle the complexity of calculating which items are visible, managing scroll position, and recycling DOM elements. The performance improvement is dramatic: a page with a 5,000-row table might have 25,000+ DOM elements without virtualization and 200 with virtualization. Every interaction on that page becomes faster because layout recalculation operates on a fraction of the elements.
CSS Containment
CSS containment (the contain property) tells the browser that an element's layout, style, or paint is independent of the rest of the page. This allows the browser to skip recalculations for contained elements when changes happen elsewhere on the page, and skip recalculations for the rest of the page when changes happen inside a contained element.
The most useful value is contain: content, which applies layout and style containment. Apply it to independent sections of your page: cards in a grid, items in a feed, sections of a dashboard. The browser can then limit the scope of layout recalculations to the affected section rather than recalculating the entire page. The newer content-visibility: auto property goes further by skipping rendering entirely for offscreen elements, but it requires explicit height hints to avoid layout shifts.
Avoiding Layout Thrashing
Layout thrashing occurs when JavaScript alternates between reading layout properties and writing style changes. Each read forces the browser to recalculate layout synchronously, and each subsequent write invalidates that calculation. The pattern looks like: read offsetHeight, write style.height, read offsetWidth, write style.width. Each read-write cycle forces a synchronous layout recalculation.
The fix is to batch reads and writes. Read all the layout properties you need first, then make all the style changes at once. Or better yet, use requestAnimationFrame to schedule DOM writes at the optimal time in the rendering pipeline. Libraries like fastdom automate this batching pattern.
Monitor your Core Web Vitals automatically
OSCOM tracks INP, LCP, and CLS across every page on your site and alerts you when metrics degrade. Real user data, not lab simulations.
Start monitoringFix 3: Taming Third-Party Scripts
Third-party scripts are the number one cause of input delay problems. Analytics tags, ad scripts, chat widgets, consent managers, and marketing pixels all execute JavaScript on the main thread. When a user interacts with your page while a third-party script is running a long task, the interaction is delayed until that task completes. You did not write the slow code, but your users experience the slowness on your site.
Audit Your Third-Party Impact
Before optimizing, quantify the impact. Use the Performance panel in DevTools with the "Third-party badges" enabled to see which scripts contribute to long tasks. The LoAF API provides programmatic third-party attribution that you can collect in production. Common offenders include Google Tag Manager containers with many tags, chat widgets that poll for updates, consent management platforms that intercept clicks, and social media embed scripts.
For each third-party script, document three things: what it does, how much main-thread time it consumes per page load, and whether it is business-critical. Many teams discover scripts that were added years ago for campaigns that ended, tools that were replaced, or features that nobody uses. Removing unused scripts is the easiest and highest-impact optimization.
Loading Strategies for Essential Third-Party Scripts
For scripts you need to keep, control when they load and execute. The default behavior of most third-party script tags is to load and execute as soon as the parser encounters them, which means they compete with your own code for main-thread time during the critical early page lifecycle when users are most likely to interact.
Use the async attribute for scripts that do not depend on other scripts and can execute in any order. Use the defer attribute for scripts that need to execute after HTML parsing is complete. For non-critical scripts (analytics, marketing pixels, chat widgets), consider delaying execution until after user interaction or after the page has been idle for a few seconds. A common pattern is loading these scripts on the first user interaction (scroll, click, or mousemove), which ensures they do not interfere with the initial INP measurement.
Web Workers for Heavy Computation
If a third-party script (or your own code) performs heavy computation that does not need DOM access, move it to a Web Worker. Web Workers run on a separate thread and cannot block the main thread. Libraries like Partytown specifically target third-party scripts by running them in a Web Worker and proxying DOM access. This approach can eliminate the main-thread impact of analytics and marketing scripts entirely, though it requires testing to ensure the proxied DOM access works correctly for each script.
Fix 4: Optimizing Event Handlers
Processing time is the phase you control most directly. Every millisecond your event handler spends executing contributes directly to INP. The goal is to make event handlers do the minimum work necessary to provide visual feedback, deferring everything else.
Separate Urgent from Non-Urgent Work
When a user clicks a button, what needs to happen immediately versus what can happen asynchronously? The immediate work is visual feedback: showing a loading state, highlighting the selection, toggling a UI element. The deferred work is everything else: API calls, analytics events, complex state derivations, data processing. By splitting event handlers into urgent (synchronous) and non-urgent (deferred) portions, you reduce the processing time that INP measures.
In practice, this means restructuring event handlers that do multiple things. Instead of one handler that updates the filter, re-sorts the list, recalculates aggregates, fires an analytics event, and updates the URL, split it into: update the filter UI immediately (urgent), then use requestIdleCallback or scheduler.postTask to handle sorting, aggregation, analytics, and URL updates. The user sees instant feedback while the heavier work happens in the background.
Debouncing Input Events
Text input fields generate a keypress event for every character typed. If each keypress triggers an expensive operation (search, filtering, validation), typing a 10-character query runs the operation 10 times. Debouncing waits until the user pauses typing (typically 150-300ms) before running the operation. This reduces the number of expensive operations and the total main-thread time consumed during a typing interaction.
The key nuance for INP is that debouncing the expensive operation does not mean delaying the visual feedback. The input field should update immediately on every keypress (this is native browser behavior and costs almost nothing). Only the downstream operation (search, filter, API call) should be debounced. This gives users responsive typing while avoiding repeated expensive computations.
Memoization and Caching
If the same computation runs repeatedly with the same inputs (re-sorting a list that has not changed, recalculating totals from unchanged data), memoize the result. In React, useMemo and useCallback prevent unnecessary recalculations during re-renders. In vanilla JavaScript, a simple Map cache keyed by the input parameters avoids recomputing values that have not changed. The processing time savings from memoization are proportional to the cost of the computation being memoized, so focus on memoizing the most expensive operations first.
Fix 5: Font and Image Optimization for Presentation Delay
Fonts and images affect INP when they cause layout shifts during interactions. A click handler that reveals content using a custom font triggers a font load, which triggers a layout shift, which extends presentation delay. Similarly, inserting images without explicit dimensions causes layout recalculation as images load and take up space.
Preload critical fonts with the link rel preload tag and use font-display: optional to prevent late-loading fonts from causing layout shifts. For images revealed during interactions (tabs, accordions, modals), use explicit width and height attributes or aspect-ratio CSS to reserve space before the image loads. These techniques do not reduce the visual quality but eliminate the layout instability that inflates presentation delay.
Framework-Specific INP Optimizations
Next.js Applications
Next.js applications have specific INP challenges and opportunities. Route transitions in the App Router trigger client-side navigation that can involve fetching RSC payloads, running layouts, and hydrating new components. These transitions are interactions that INP measures. Use loading.tsx files to provide instant visual feedback during route transitions, and keep page components lightweight so hydration is fast.
Third-party scripts loaded via the next/script component should use the strategy="lazyOnload" option for non-critical scripts like analytics. This defers loading until after hydration, reducing main-thread contention during the initial interaction window. For components that render large lists or complex visualizations, use dynamic() with ssr: false to load them client-side only, avoiding hydration mismatches and reducing the initial JavaScript payload.
React Applications (General)
React 18+ concurrent features are the primary tools for INP optimization. startTransition marks state updates as non-urgent, allowing React to interrupt them if a higher-priority update (user interaction) arrives. useDeferredValue creates a deferred version of a value that updates with lower priority. Both mechanisms let React yield to the main thread during renders, preventing long tasks that would increase INP.
Component-level code splitting with React.lazy and Suspense reduces the JavaScript that needs to parse and execute during hydration. Every kilobyte of JavaScript that does not need to parse during the initial load is main-thread time saved, which translates directly to lower input delay for early interactions.
Single-Page Applications (Vue, Angular, Svelte)
The same principles apply across frameworks. Vue 3's computed properties and watchEffect with flush: post provide similar deferred update patterns. Angular's OnPush change detection and signals reduce unnecessary re-renders that inflate processing time. Svelte's compile-time reactivity generally produces lighter event handlers than runtime-reactive frameworks, but complex Svelte components with many reactive declarations can still produce long tasks during interactions.
Monitoring INP in Production
Fixing INP once is not enough. New features, new third-party scripts, and code changes can regress INP at any time. Continuous monitoring is essential to maintain good INP scores over time.
Setting Up Continuous INP Monitoring
Deploy the web-vitals library on all pages and send INP data to your analytics or observability platform. At minimum, track: page URL, INP value, interaction type (click/keypress/tap), target element selector, and the input delay / processing time / presentation delay breakdown. Aggregate by page template (not individual URL) to identify patterns across similar pages.
Set up alerts for when p75 INP exceeds your threshold on any page template. The alert should trigger when the 75th percentile crosses 200ms (the "good" boundary) for three consecutive days, accounting for weekend traffic patterns that may produce different interaction profiles. Include the specific page template and the most common slow interaction in the alert so engineers can immediately start investigating.
CI/CD Integration
While lab data does not perfectly predict field INP, it can catch regressions before they reach production. Integrate Lighthouse CI or web-vitals benchmarking into your deployment pipeline. Run interaction simulations (using Puppeteer or Playwright to click buttons and type in fields) and measure the INP of each interaction. Block deployments that introduce interactions above a lab-measured threshold (250ms is a reasonable lab threshold that correlates with 200ms in the field).
The lab tests should cover the specific interactions identified as slow from field data. A generic Lighthouse audit does not test specific interactions and will not catch most INP regressions. Script the exact user flows that have historically produced slow interactions and run those as performance tests.
INP Optimization Prioritization Framework
Not all INP improvements are equally valuable. Prioritize fixes based on three factors: the number of users affected (page traffic), the severity of the problem (how far above the threshold), and the feasibility of the fix (effort required). A page with 100,000 monthly visitors and a p75 INP of 800ms should be fixed before a page with 500 visitors and a p75 INP of 250ms, even if the second fix is easier.
| Fix Category | Typical Impact | Effort | Priority |
|---|---|---|---|
| Remove unused third-party scripts | 50-200ms reduction | Low (hours) | Do first |
| Defer non-critical scripts | 30-150ms reduction | Low (hours) | Do first |
| Add startTransition to non-urgent updates | 50-300ms reduction | Medium (days) | Do second |
| Virtualize large lists | 100-500ms reduction | Medium (days) | Do second |
| Break long tasks with yielding | 50-200ms reduction | Medium (days) | Do second |
| Move scripts to Web Workers | 100-300ms reduction | High (weeks) | Do if needed |
The SEO Impact of Passing INP
Core Web Vitals are a confirmed Google ranking factor, though their weight relative to content relevance and backlinks is modest. The direct ranking impact of fixing INP is typically small. Where INP improvements produce measurable SEO gains is indirect: faster interactions reduce bounce rates, increase pages per session, and improve conversion rates. These engagement signals may influence rankings more than the Core Web Vitals signal itself.
The more concrete business impact is in the CWV badge. Pages that pass all three Core Web Vitals (LCP, INP, CLS) receive a "good" page experience signal in Search Console. While Google has not confirmed a direct badge-based ranking boost, pages that fail Core Web Vitals are at a competitive disadvantage when all other factors are equal. For competitive queries where ten pages are roughly equal in content quality and authority, Core Web Vitals can be the tiebreaker.
Key Takeaways
- 1INP measures the complete interaction lifecycle (input delay + processing time + presentation delay), not just the initial delay. This is why sites that passed FID may fail INP. The target is under 200ms at the 98th percentile.
- 2Diagnosis before optimization is essential. Use CrUX for page-level data, RUM with the web-vitals library for interaction-level attribution, and Chrome DevTools for root cause analysis. Know exactly which interactions are slow and why before writing code.
- 3The highest-impact fixes in order: remove unused third-party scripts, defer essential third-party scripts, use React concurrent features (startTransition, useDeferredValue) for non-urgent updates, virtualize large lists, and break long tasks with yield patterns.
- 4DOM size directly affects presentation delay. Every element on the page makes style recalculation and layout more expensive. Keep total DOM under 1,400 elements using virtualization, lazy loading, and CSS containment.
- 5Monitor INP continuously in production with field data collection and alerting. Lab data catches regressions pre-deployment, but field data is the source of truth for real user experience.
Technical SEO and performance insights for growth teams
Core Web Vitals optimization, site speed, crawl efficiency, and technical SEO frameworks. Actionable guides delivered weekly.
INP optimization is not a one-time project. It is an ongoing discipline, similar to maintaining test coverage or managing technical debt. Every new feature, every new third-party integration, and every framework upgrade has the potential to regress INP. The teams that consistently pass Core Web Vitals are the ones that measure INP continuously, investigate regressions immediately, and treat responsiveness as a product requirement rather than an afterthought. The good news is that the diagnostic tools have matured significantly. Between the web-vitals library, Chrome DevTools Interactions track, and the Long Animation Frames API, you have complete visibility into what makes your pages slow. The fixes are well-understood and mostly mechanical. The hard part is building the organizational habit of monitoring and responding to INP data consistently over time.
Find where you're losing traffic and what to fix first
Oscom SEO scores every keyword across 6 dimensions and shows you the highest-value opportunities you're missing right now.