Blog

How to fix INP (Interaction to Next Paint): find the slow interaction, name the script, fix the cause

INP is good at 200 ms or less for 75% of visits. Find the slow tap and the script behind it with field data, then fix input delay, handlers or rendering.

By Nicolás Cerón ·

A dark greenhouse at night where a single crimson cord, just pulled, swings a small bell under warm lamplight.
On this page

The short answer

Interaction to Next Paint (INP) is good when 75% of visits respond to clicks, taps and key presses within 200 milliseconds, and poor above 500 milliseconds. To fix it, first find the slow interaction and the script behind it with field data (the attribution build of web-vitals and the Long Animation Frames API), then fix the part of the interaction that's long: input delay (other work blocking the main thread, usually during load), processing (your event handlers) or presentation (the rendering work that follows).

Most poor INP on marketing sites comes from three places: third-party tags running when someone taps, framework hydration during load, and handlers that do everything before the browser can paint. Breaking long tasks up and yielding to the main thread fixes most of it.

INP is mostly a phone problem. In September 2026, 97.6% of sites had good INP on desktop, but 79.4% on mobile, and 62.9% in Colombia, 55.3% in Brazil and 59.7% of Next.js sites worldwide (data below).

This guide goes deep on INP only. For all three Core Web Vitals and how to verify a fix in Google's 28-day data, see our Core Web Vitals playbook.

What INP measures, and what it doesn't

INP observes every click, tap and key press during a visit and reports the slowest one, ignoring one outlier for every 50 interactions (web.dev). Hovering, scrolling and zooming don't count. A page that nobody interacted with has no INP at all, so pages with little to click often show no INP data.

The thresholds, measured at the 75th percentile of visits, separately for mobile and desktop:

INP at the 75th percentileRating
200 ms or lessGood
Over 200 ms, up to 500 msNeeds improvement
Over 500 msPoor

INP replaced First Input Delay as a Core Web Vital on 12 March 2024 (web.dev). The difference matters for diagnosis: FID only timed the wait before the first interaction's handlers started. INP times every interaction, from the tap to the next painted frame, in three parts:

PartWhat it isUsually caused byFirst fix
Input delayTap until your handlers startLong tasks already running: script evaluation, hydration, tags, timersBreak up and defer work during load
Processing durationYour handlers runningHandlers that do everything at once; forced layoutPaint the visible change first, yield, then do the rest
Presentation delayHandlers done until the frame is paintedLarge DOM, big style and layout recalculations, client-side HTML renderingSmaller updates, content-visibility, less DOM

Two details catch teams out. Interactions inside iframes count toward the page's INP in Chrome's data, but JavaScript on the page can't see them, so your own measurement and CrUX can differ (web-vitals). And a long task is any task over 50 milliseconds; the browser can't respond to input until the current task finishes (web.dev).

How sites score on INP, by platform and country

We read the HTTP Archive's technology report API on 17 October 2026. Its latest release, September 2026, is built from the Chrome UX Report: the share of sites (origins) on each platform whose real Chrome visitors on phones got a good INP. Our website platform speed report shows the same data for every platform and metric, updated monthly.

Platform (mobile, good INP)All countriesUnited StatesMexicoColombiaBrazil
Squarespace97.3%98.6%81.4%81.0%82.8%
Wix92.6%96.6%82.8%82.9%83.9%
WordPress90.3%92.6%78.2%77.1%78.8%
Webflow88.0%92.4%68.0%57.9%61.9%
Shopify86.7%95.1%71.5%56.0%64.8%
HubSpot CMS86.2%92.4%63.1%57.7%55.5%
Astro85.0%92.6%68.7%65.3%67.7%
Framer80.3%88.2%56.8%37.9%57.6%
All sites79.4%87.3%66.4%62.9%55.3%
Gatsby76.0%78.1%57.6%51.6%55.2%
Nuxt64.1%83.9%45.8%38.7%32.4%
Next.js59.7%75.9%43.5%37.4%40.2%
VTEX39.3%57.3%32.5%23.7%40.5%

Source: HTTP Archive technology report, September 2026 release (Chrome UX Report field data), mobile. Colombia's Framer row covers 116 sites; every other cell covers more than 120.

What the table says:

  • INP is a phone problem. On desktop, 97.6% of all sites had good INP; on mobile, 79.4%. Next.js sites drop from 94.2% on desktop to 59.7% on mobile. Same code, slower processors.
  • JavaScript frameworks fail INP; WordPress fails elsewhere. 90.3% of WordPress sites had good INP, but only 48.3% passed all three Core Web Vitals on mobile, so WordPress's problem is mostly loading. For Next.js (35.7% passing overall) and Nuxt (28.8%), INP is the metric that fails.
  • The country moves the number more than the platform. A Webflow or Shopify site that's good for 92–95% of US sites is good for 56–58% of sites measured in Colombia. Our Latin America Core Web Vitals study explains why: the same pages, run on mid-range Android phones.
  • It's improving, unevenly. Across all sites, good mobile INP rose from 71.6% in March 2024 to a peak of 82.3% in April 2026, then eased to 79.4% by September 2026. Chrome 147 changed how interactions are recorded so that fewer are dropped from CrUX; Chrome says this doesn't change individual durations but raises coverage (Chromium changelog). This data can't tell how much of the dip that explains. Brazil's figure fell furthest, from 65.1% in April to 55.3% in September, partly because about 30,000 more Vue.js sites, only 15.8% of them with good INP, entered the Brazilian data in July. Next.js improved most over the whole period: from 37.3% in March 2024 to 59.7%.

The platform is a correlation, not a verdict. A Next.js site is more often an app with filters and logged-in state; a Squarespace site is more often a brochure. Your own template's data is what decides.

Sites that carry common third-party tags

The same report can be filtered by technologies detected on a site, including tags. These are the shares of sites with good mobile INP among sites that carry each tag, worldwide, against 79.4% for all sites:

Tag on the siteCategorySites measuredGood INP (mobile)
CookieYesConsent banner178,40892.4%
CookiebotConsent banner122,86888.8%
Google Tag ManagerTag manager2,082,03777.7%
OneTrustConsent banner97,35275.6%
ZendeskChat and support152,50475.8%
Facebook PixelAds1,291,61673.7%
HotjarSession recording153,39573.1%
Microsoft ClaritySession recording485,30568.8%
VWOA/B testing19,73565.3%
IntercomChat and support18,95060.2%
OptimizelyA/B testing21,04857.6%
TikTok PixelAds169,71051.4%

Source: HTTP Archive technology report, September 2026, all countries.

Read this with care: it describes sites that carry a tag, not the tag's own cost. Sites with Optimizely or Intercom tend to be larger sites with more of everything, and consent tools used on small WordPress sites inherit those sites' good numbers. What holds regardless: Google's tag guidance reports "a correlation between the size of tag managers and poorer INP scores" (web.dev), and A/B testing, chat and session recording all run code in the page while people use it.

Step 1: confirm INP is the problem, and on which template

Start with Google's field data, mobile first:

  • Search Console's Core Web Vitals report groups similar URLs and shows each group's INP at the 75th percentile over 28 days. Groups are sorted by impressions, so the first example URL tells you which template to fix first. A group can appear without INP data, because Search Console only requires enough LCP and CLS data to list it.
  • PageSpeed Insights shows the same Chrome data for one URL, or for the whole origin when the URL has too few visits. Check which one you're reading.
  • No field data at all usually means the site has too few Chrome visitors to be included. Then your own measurement is the only field data you'll have.

The Web Almanac adds a useful prior: in 2025, 80% of mobile home pages had good INP, but only 69% of other pages (HTTP Archive). Listing pages, product pages and anything with filters are where to look.

Step 2: find the interaction and the script, in the field

CrUX tells you that INP is poor, not why. For why, collect attribution from real visits. The web-vitals library (version 6.2.3, released 5 October 2026) has an attribution build that reports, for the slowest interaction of each visit: the element, the three parts, whether the page was still loading, and the longest script that overlapped it.

import {onINP} from 'web-vitals/attribution';

onINP(({value, rating, attribution: a}) => {
  const script = a.longestScript?.entry;
  navigator.sendBeacon('/rum', JSON.stringify({
    page: location.pathname,
    inp: Math.round(value),
    rating,
    target: a.interactionTarget,       // which element was tapped
    loadState: a.loadState,            // was the page still loading?
    inputDelay: Math.round(a.inputDelay),
    processing: Math.round(a.processingDuration),
    presentation: Math.round(a.presentationDelay),
    scriptURL: script?.sourceURL,      // your bundle or a vendor's
    scriptInvoker: script?.invoker,    // e.g. BUTTON.onclick
    scriptPart: a.longestScript?.subpart,
  }));
});

The script fields come from the Long Animation Frames API (LoAF), shipped in Chrome 123. It reports frames delayed beyond 50 milliseconds and names each script that ran more than 5 milliseconds in them, with its URL, function name and how it was called. That's how you tell your own code from a vendor's. Its limits: no attribution for scripts inside cross-origin iframes, and it exists only in Chromium browsers.

You can also watch LoAF directly while you test:

new PerformanceObserver((list) => {
  for (const frame of list.getEntries()) {
    if (frame.blockingDuration < 100) continue;
    for (const s of frame.scripts) {
      console.log(Math.round(s.duration), s.invokerType, s.invoker, s.sourceURL);
    }
  }
}).observe({type: 'long-animation-frame', buffered: true});

Measurement now reaches beyond Chrome: the web-vitals README lists onINP() as supported in Chromium, Firefox and Safari, now that both of the latter expose the interaction data it relies on. Google's INP numbers still come from Chrome users only, but you can now see whether iPhone visitors have the same slow interaction.

Then group the data by target element, load state and the longest part. The pattern points at the fix:

What the data showsLikely causeGo to
Long input delay, loadState "dom-interactive" or "dom-content-loaded"Script evaluation, hydration or tags during loadLong tasks during load; hydration
Long input delay after load, scriptURL a vendor'sA tag's timers or listenersThird-party tags
Long processing, scriptURL your bundleThe handler itselfExpensive event handlers
Long processing with forced style and layoutReading layout after changing stylesLayout thrashing
Long presentation delayToo much to re-renderLarge DOM and big updates

Step 3: reproduce it in Chrome DevTools

Open the Performance panel. Without recording, it shows your local INP as you click and lists each interaction with its element and its three parts. Connect it to field data and it compares your local numbers with your real visitors' and suggests settings to match them.

Slow the CPU to match the phones your visitors use: DevTools can calibrate CPU throttling presets to approximate low- and mid-tier mobile devices. Then record while you tap the element your field data named, ideally while the page is still loading, since that's when the main thread is busiest. In the recording, find the interaction in the Interactions track and read the main-thread tasks beneath it; long tasks carry a red triangle (web.dev).

A Lighthouse page-load test doesn't tap anything, so it can't measure INP. Google suggests its Total Blocking Time as a proxy for load-time input delay, not a substitute (web.dev).

Fixes, by cause

Long tasks: break them up and yield

The browser can't respond while a task runs, so split long work and give the main thread back between pieces. scheduler.yield() does that and resumes your code ahead of other queued work. It's in Chrome and Edge 129 and Firefox 142, not in Safari as of October 2026, so use it with a fallback (web.dev):

function yieldToMain() {
  if (globalThis.scheduler?.yield) return scheduler.yield();
  return new Promise((resolve) => setTimeout(resolve, 0));
}

filterButton.addEventListener('click', async () => {
  showSpinner();            // the visible response
  await yieldToMain();      // let the browser paint it
  renderResults(filterProducts(state));
  await yieldToMain();
  sendAnalytics('filter_applied');
});

For loops over many items, yield only when about 50 milliseconds have passed since the last yield, so the yielding overhead doesn't add up. Two traps: forEach doesn't wait for an awaited yield, so use for…of; and Google no longer recommends isInputPending().

Third-party tags

Tags hurt INP two ways: their scripts make input wait during load, and their listeners run when people click.

  • The consent banner's Accept button. Accepting often starts every tag at once. A consent tool can yield right after the click so the banner closes in the next frame; PubTech reported that doing so reduced INP on its consent tool's customer sites by up to 64% (web.dev case study). If your vendor doesn't, ask.
  • Fire non-essential tags after Window Loaded in Google Tag Manager, and prefer image tags and templates to Custom HTML tags, which Google ranks as the costliest type (web.dev).
  • Remove what nobody reads. Session recording, heatmaps and A/B testing scripts that ran for a past project are common. QuintoAndar, the largest housing platform in Brazil, removed third-party pixels as part of the work that cut its INP by 80% (web.dev case study).

Use LoAF's sourceURL to name the vendor before you remove anything, so the decision rests on data rather than suspicion.

Hydration of big frameworks

Server-rendered pages look ready before they are: the page "can't actually respond to input until the client-side scripts for components are executed and event handlers have been attached," and that cost shows up in INP (web.dev). That matches the table above, where the JavaScript-framework rows sit lowest.

  • Ship less component JavaScript. In Next.js, every import of a file marked 'use client' ends up in the client bundle (Next.js docs), so keep that boundary low in the tree, around the interactive part only.
  • Hydrate later, or only what's interactive. Astro renders static HTML and hydrates only the components you mark, when you choose (Astro docs):
<ProductFilters client:visible />
<NewsletterSignup client:idle />
  • Mark non-urgent React updates as transitions with useTransition, so typing stays responsive while results re-render. QuintoAndar used transitions this way (web.dev case study):
const [isPending, startTransition] = useTransition();

function onSearchChange(e) {
  setQuery(e.target.value);                          // urgent: the input
  startTransition(() => setResultsFilter(e.target.value)); // can wait
}

If a framework is the wrong tool for a mostly static marketing site, our comparison of Astro and Next.js covers when each fits.

Expensive event handlers

Do only what the next frame needs inside the handler, and move the rest after the paint. Debounce handlers that fire repeatedly, such as scroll, input and resize. And watch how much work one interaction triggers: bus-ticket site redBus reported that fetching 10 results per lazy load instead of 30 brought those interactions from 870–900 to 350–370 milliseconds (web.dev case study).

Layout thrashing

Reading a layout property (offsetWidth, getBoundingClientRect(), scrollHeight) after changing styles forces the browser to lay out the page on the spot. In a loop, it does that every time (web.dev):

// Forces a layout on every iteration after the first
for (const bar of bars) {
  bar.style.width = container.offsetWidth * bar.dataset.share + 'px';
}

// Read once, then write
const width = container.offsetWidth;
for (const bar of bars) {
  bar.style.width = width * bar.dataset.share + 'px';
}

LoAF reports it as forcedStyleAndLayoutDuration on each script, and DevTools marks the Layout or Recalculate Style task with a red triangle.

Large DOM and big rendering updates

Rendering work grows with DOM size, so render menus, tabs and long lists when they're needed rather than all up front. The Economic Times removed about 1,200 nodes by rendering its menu only when opened, part of the work that took its INP from over 1,000 to 257 milliseconds (web.dev case study).

For long pages, content-visibility: auto skips rendering for sections far from the viewport; in Google's demo, rendering went from 232 to 30 milliseconds on load (web.dev). Give each section a placeholder size so the scrollbar doesn't jump:

.long-page section {
  content-visibility: auto;
  contain-intrinsic-size: auto 900px;
}

The same web.dev page lists support in Chrome 85, Firefox 125 and Safari 18. Off-screen content stays in the accessibility tree and stays searchable with find-in-page.

Verify the fix

Your own measurement shows a fix within days. Google's data won't: Search Console and PageSpeed Insights read a rolling 28-day window, so a fix shipped today is fully reflected about four weeks later. Ship one INP fix per release when you can, and note the date, so you know which change moved the number. Our Core Web Vitals playbook covers validation in Search Console step by step.

INP checklist

StepDone when
Find the failing templateSearch Console names the URL group with poor INP on mobile
Collect attributionField data shows the element, the longest part and the script for the slowest interactions
ReproduceThe same interaction is slow in DevTools with calibrated CPU throttling
Fix load-time input delayNo long tasks from tags or hydration where people tap first
Fix handlersThe visible change paints before the rest of the work runs
Fix renderingNo forced layouts in handlers; long pages use content-visibility
VerifyYour own data improves within days; Google's 28-day data about four weeks after release

What to do next

Check how sites on your platform do in your visitors' country in our website platform speed report, then add the web-vitals attribution snippet above to your site and let it collect a week of data. If you'd rather hand the diagnosis and fixes over, that's our website speed optimization service, and deeper rebuilds of slow interfaces are web development work.

Common questions

What is a good INP score?

200 milliseconds or less at the 75th percentile of visits, measured separately for mobile and desktop. Between 200 and 500 milliseconds needs improvement, and above 500 milliseconds is poor. Google's tools report the value over a rolling 28 days.

What is the difference between INP and FID?

First Input Delay only measured the wait before the first interaction's handlers started. INP measures every click, tap and key press in a visit, from the input to the next painted frame, and reports the slowest one, ignoring one outlier per 50 interactions. INP replaced FID as a Core Web Vital on 12 March 2024.

Why is my INP poor on mobile but good on desktop?

Phones run the same JavaScript on slower processors, so a task that's short on a laptop can become a long task on a mid-range Android phone. In September 2026, 97.6% of sites had good INP on desktop and 79.4% on mobile. Test with CPU throttling calibrated to a mid-tier phone.

Why does PageSpeed Insights show no INP for my page?

INP only exists when visitors click, tap or type, and Chrome's data only covers pages and sites with enough eligible visits. A page few people interact with, or a small site, may have no INP data at all. Measure it on your own visits with the web-vitals library instead.

Does scheduler.yield() work in every browser?

No. As of October 2026 it works in Chrome and Edge 129 and later and Firefox 142 and later, but not in Safari. Use a small helper that calls scheduler.yield() when it exists and falls back to a setTimeout promise, so every browser still yields.

Can a plugin fix INP?

Rarely on its own. Caching and image plugins speed up loading, but INP depends on which scripts run when someone taps, how much work each handler does and how much the page re-renders. Those live in your templates, components and tag settings, which is where INP gets fixed.

Sources & further reading

Sources behind this page, with further detail from the original publishers.