How to improve Interaction to Next Paint

You want clicks and taps to feel instant. INP is the bar: your slowest interaction should complete within 200 ms at the 75th percentile. Here is how to find the bottlenecks and fix them on a JavaScript heavy site.

By , founder of Porteur · Updated 14 September 2026 · Markdown

What INP actually measures

INP tracks every click, tap and key press in a visit, then reports the slowest one. For very interactive pages it discards one outlier per fifty interactions so one glitch does not decide the score.

  • Good up to 200 ms, poor above 500 ms, as of 2026.
  • It replaced First Input Delay in March 2024.
  • An interaction has three parts: input delay, processing, presentation delay.

Input delay is the wait for the main thread to free up. Processing is your handlers running. Presentation delay is the time to paint the next frame. Any of the three can dominate.

A poor INP means someone clicked and nothing seemed to happen for too long. On a signup form that might be the first key press. On /pricing it might be “Start trial”.

Field data versus lab, and where to read INP

Google assesses Core Web Vitals in the field. It uses the 75th percentile of real Chrome users over 28 days, per URL or per origin. That dataset is the Chrome User Experience Report. It includes Android and desktop, not iOS.

  • Search Console, Core Web Vitals report: field data at URL or origin level with pass or fail.
  • PageSpeed Insights, top section: the same field data when there is enough traffic.
  • A URL with too few visits falls back to the origin or shows nothing.

Lighthouse is lab only. It simulates one load on a mid range phone over throttled 4G. It cannot interact with your page, so it cannot measure INP. Use it to diagnose, not to judge pass or fail in Search.

Example: yourproduct.com/docs gets a green INP in the field but a middling Lighthouse score. Ship based on the field. Fix based on the lab hints that affect interactivity.

Why Lighthouse cannot see INP, and what to use instead

Lighthouse does not click your buttons. It runs once, logs timings, and gives a score. Its performance score weights Total Blocking Time 30%, Largest Contentful Paint 25%, Cumulative Layout Shift 25%, First Contentful Paint 10% and Speed Index 10%. Scores vary between runs.

Use Total Blocking Time as the proxy in the lab. TBT sums up blocking time above 50 ms during load. It correlates with input delay. Good up to 200 ms in the lab is a useful target while you test changes.

  • Fix the long tasks Lighthouse lists.
  • Cut third party costs in the audit “Reduce the impact of third party code”.
  • Aim for TBT under 200 ms on Mobile to improve real INP.

How to find slow interactions on your pages

Start with the field data to pick pages. Then use tools that capture real clicks and DevTools to inspect the main thread. Work from a real device when you can.

  1. Pick targets from field data

    Open PageSpeed Insights for /pricing, /checkout and /guides/getting-started. Switch to Mobile. Note the INP rating on each. If the URL shows origin level data, test more than one page.

  2. Use the Web Vitals extension

    Install the Web Vitals Chrome extension. Load your page on a mid range Android or in a throttled profile. Click common UI: open the menu, add to basket, start the trial. Watch the INP readout and the overlay that flags slow interactions.

  3. Record a DevTools performance trace

    Open DevTools, Performance. Tick Web Vitals. Start recording, then reproduce the slow click. Stop. In the main thread lane, find long tasks around your click. Expand the task to see the call stack and durations.

  4. Test with a clean profile

    Disable extensions and ad blockers. Test again. Third party blockers can hide or change script cost. You want a trace that matches your field users.

  5. Repeat on Desktop

    Switch PSI to Desktop. Repeat the click and trace on a lower end laptop if your product has many desktop users.

A good trace shows a short handler followed by a quick paint after your click. A bad one shows a long task before anything draws, or a handler that runs heavy work before a paint.

Typical causes of poor INP on JavaScript heavy sites

  • Hydration that blocks the main thread before the first click.
  • Long tasks during or after load, often from bundles and polyfills.
  • Heavy event handlers attached to common UI, like onClick for navigation that does work before routing.
  • A large DOM that triggers slow style recalculation and layout.
  • Synchronous layout reads after writes that cause forced reflow in handlers.
  • Third party scripts that run on every click, like analytics, consent and A/B tests.

Hydration can delay the first click on a page rendered by a framework. If it takes seconds, the first tap cannot do anything. That tap becomes the slowest interaction in the visit.

A large DOM magnifies work after every change. A dropdown that touches many nodes can trigger long style and layout passes. Your click handler seems light, but the browser is busy recalculating.

Third parties can add costs you do not see in your code. A tag manager container that fires on click may add blocking time before paint. Remove or delay where you can.

Fixes that move INP under 200 ms

You have three levers: unblock the main thread, make handlers small, and show the visual change before heavy work. Do the minimum to paint, then finish in the background.

  • Break long tasks into chunks with scheduler.yield or setTimeout.
  • Render first, then work: apply the DOM change, let the browser paint, then continue.
  • Cut JavaScript: smaller bundles, fewer dependencies, code split the routes that are not used.
  • Move work off the main thread with Web Workers for parsing, data transforms or image work.
  • Reduce DOM cost: fewer nodes, simpler CSS selectors, avoid layout thrash.
// Break a long task into chunks so clicks can be handled
async function processBigList(items) {
  for (let i = 0; i < items.length; i++) {
    heavyWork(items[i]);
    if (i % 50 === 0) {
      await scheduler.yield(); // give control back to the main thread
    }
  }
}

// Fallback if scheduler.yield is not available
function chunkedWork(items, i = 0) {
  const start = performance.now();
  while (i < items.length && performance.now() - start < 8) {
    heavyWork(items[i++]);
  }
  if (i < items.length) setTimeout(() => chunkedWork(items, i));
}
// Render first, then do heavy follow-up
button.addEventListener('click', async () => {
  // 1. Immediate feedback
  button.disabled = true;
  button.textContent = 'Working…';

  // 2. Let the browser paint that change
  await new Promise(requestAnimationFrame);

  // 3. Do the heavy work in chunks or off-thread
  await doWorkInWorker();

  // 4. Update UI again
  button.textContent = 'Done';
  button.disabled = false;
});

On /checkout, move form validation that does network calls to after the first visual response. Disable the button and show a spinner first, then validate asynchronously. Your click now feels instant and the work continues without blocking the paint.

Framework and hydration strategy

If hydration is your bottleneck, change when and how it runs. You want the UI to accept input fast, then hydrate the parts that need JavaScript.

  • Start with server rendered HTML, so the first paint is quick.
  • Defer hydration for below the fold islands and widgets.
  • Hydrate on idle for non interactive parts. Hydrate on interaction for complex widgets that start closed.
  • Remove client side work that duplicates server work.
  • Ship fewer components to the client by moving logic to the server when possible.

Example: on /guides, hydrate the table of contents only when the user opens it. The first scroll and first tap happen without waiting for the whole page to wire up.

Trim JavaScript and third parties

Every byte of script can delay work on the main thread. Smaller bundles mean fewer parse and compile costs, and fewer long tasks during and after load. Third parties add work you cannot control, so be strict.

  1. Defer and async

    Use defer or type=module on scripts that do not need to block parsing. Async for independent scripts. Keep synchronous scripts out of the head.

  2. Code split by route and component

    Load only what /pricing needs. Do not ship checkout code to the blog. Split large components so the common path stays light.

  3. Remove unused and duplicate code

    Audit your bundle. Drop polyfills not needed by your browsers. Remove libraries that duplicate native features.

  4. Control the tag manager

    Keep the container small. Fire tags after load, or on interaction. Remove tags nobody reads in your reports.

  5. Lazy load embeds with facades

    Replace YouTube iframes with a thumbnail and a play button. Load the player on click. Do the same for maps.

PageSpeed Insights will list heavy third party time under “Reduce the impact of third party code”. Each origin there is a candidate to remove, defer or load on interaction to avoid blocking your next paint after a click.

Render path basics that affect your first interaction

A slow first paint and heavy layout can make your first click the slowest of the visit. Fix these so the page is responsive before the user interacts.

  • Largest Contentful Paint: aim for 2.5 s or less. Do not lazy load the hero image. Use fetchpriority=high or a preload for CSS backgrounds.
  • Render blocking resources: inline critical CSS, load the rest non blocking. Defer or async scripts. Avoid @import in CSS.
  • Fonts: use font display swap or optional. Preload the face used above the fold. Match fallback metrics to prevent shifts.
  • TTFB: cache HTML, use a CDN, and avoid redirect chains. HTTP/2 or HTTP/3 and keep alive help.

A fixed page paints fast, accepts input, and shows an immediate visual response. The details vary by stack, but the principles are the same: unblock the main thread and only do the work that must run now.

Measure, ship, and watch the 28 day window

Make one change at a time. Verify in the lab that TBT goes down. Test real clicks on a phone. Ship, then wait for the field data to catch up.

  • Aim for INP under 200 ms at the 75th percentile on Mobile.
  • Watch Search Console’s Core Web Vitals report for the URL. It uses 28 days of data.
  • If a page has too little traffic, add internal links and test the origin level trend.

Do not chase a perfect Lighthouse score. A passing Core Web Vitals assessment at the field thresholds is what matters for Search. The SEO lift is smaller than relevance and content, but pages that feel instant convert better.

Questions

Sources

Check my site, free

Paste your URL to get a free INP check that reads your site, the searches around it and rivals on them in about thirty seconds, and shows three findings; connect Search Console later in Porteur if you want deeper data.

  • Free check, no card
  • Read-only, your own accounts
  • Readable by your agent

Read next