A page can load quickly and still feel broken: you tap "Add to cart" and nothing happens for half a second, or a menu opens a beat too late. Interaction to Next Paint (INP) measures exactly that lag. On mobile it is mostly a JavaScript problem: slower phone CPUs turn small delays into noticeable ones. This guide covers how to find the slow interaction, work out which part of it is slow, and fix it.
The target is an INP of 200 ms or less at the 75th percentile of real visits. Over 500 ms is rated poor.
Key takeaways
- Aim for an INP of 200 ms or less at the 75th percentile of real visits, as anything over 500 ms is rated poor.
- Lighthouse cannot measure INP, so use field data and real-user attribution to find which interaction is slow.
- Break up long tasks by showing visible feedback first, then yielding to the main thread before doing the rest of the work.
- Audit your tag manager and load chat widgets and embeds only on interaction, as third-party scripts are often the biggest problem.
What INP actually measures
INP watches every click, tap and key press during a visit (not hover or scroll) and reports roughly the slowest one. Each interaction breaks into three phases:
| Phase | What happens | Typical cause when slow |
|---|---|---|
| Input delay | The browser waits for the main thread to be free before running your handler | Other JavaScript is running: page startup, third-party tags, timers |
| Processing duration | Your event handlers run | Too much work in the click or input handler |
| Presentation delay | The browser calculates layout and paints the next frame | Huge DOM, expensive CSS, rendering lots of HTML from JavaScript |
Fixing INP means identifying which phase dominates for your worst interaction. Shaving processing time does nothing if the real problem is a tag manager hogging the main thread.
Step 1: Find the slow interaction
Start with field data
INP is a field metric. Lighthouse cannot measure it, because a lab test does not click anything; it reports Total Blocking Time (TBT) as a proxy. High TBT suggests INP trouble, but you need real-user data to know which interaction is slow.
- PageSpeed Insights shows your INP from the Chrome User Experience Report (CrUX) if you have enough traffic. It tells you whether you have a problem, not where.
- Real User Monitoring (RUM) tells you which element was interacted with, whether it happened during page load, and the phase breakdown. Many analytics and RUM tools now report INP attribution. You can also collect it yourself:
<script type="module">
import { onINP } from 'https://unpkg.com/web-vitals@5/dist/web-vitals.attribution.js?module';
onINP(({ value, attribution }) => {
console.log('INP', Math.round(value), 'ms', {
target: attribution.interactionTarget,
inputDelay: attribution.inputDelay,
processing: attribution.processingDuration,
presentation: attribution.presentationDelay,
});
});
</script>
In production, send these values to your analytics instead of the console.
Reproduce it in the lab
Once you know the likely culprit, reproduce it in Chrome DevTools:
- Open the Performance panel. Turn on CPU throttling (4x or 6x slowdown) to approximate a mid-range phone.
- The live metrics view shows INP as you interact. Click around: menus, filters, add-to-cart, form fields.
- Interact while the page is still loading too. The main thread is busiest then, and many real-user INP problems happen in the first few seconds.
- Record a trace of the slow interaction and look at the Interactions track. It shows the input delay, processing and presentation phases, and the long tasks underneath.
Step 2: Fix long tasks (input delay)
A long task is anything that occupies the main thread for more than 50 ms. While a long task runs, the browser cannot respond to input. If a user taps during a 400 ms task, that tap waits.
Common sources:
- Large JavaScript bundles being parsed and executed during startup
- Page builder and theme scripts that initialize every widget on load
setIntervaltimers doing heavy work repeatedly- Third-party scripts (covered below)
Fixes:
- Ship less JavaScript. Remove unused libraries, split bundles by route, and load widgets only on pages that use them.
- Defer non-essential scripts with
deferor load them after the page is interactive. - Avoid recurring timers that do heavy work. Replace polling with events where possible.
Step 3: Yield to the main thread (processing duration)
If your own event handler does too much, split the work so the browser can paint between pieces. The pattern: do the visible update first, yield, then do everything else.
function yieldToMain() {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
button.addEventListener('click', async () => {
// 1. Visible feedback first
button.classList.add('is-loading');
updateCartCount();
// 2. Let the browser paint
await yieldToMain();
// 3. Everything else
saveCartToServer();
sendAnalyticsEvent();
});
scheduler.yield() is supported in Chrome and Edge 129+ and Firefox 142+, but is not yet available everywhere, so always feature-detect as above. Its advantage over setTimeout is that your remaining work resumes ahead of other queued tasks, so third-party scripts do not jump in between.
For loops over large data sets, yield only when a time budget runs out, because yielding has overhead:
async function processItems(items) {
let lastYield = performance.now();
for (const item of items) {
processItem(item);
if (performance.now() - lastYield > 50) {
await yieldToMain();
lastYield = performance.now();
}
}
}
Other processing-time fixes:
- Debounce input handlers on search boxes and filters so they do not run on every keystroke.
- Cancel stale work. Use an
AbortControllerto cancel outdatedfetchrequests when the user types again, so old responses do not trigger extra rendering. - Avoid layout thrashing. Reading
offsetHeightorgetBoundingClientRect()right after changing styles forces the browser to recalculate layout synchronously. Batch all reads, then all writes.
// Slow: write, read, write, read...
items.forEach((el) => {
el.style.width = '100px';
console.log(el.offsetHeight);
});
// Better: read everything, then write everything
const heights = items.map((el) => el.offsetHeight);
items.forEach((el) => { el.style.width = '100px'; });
Step 4: Reduce rendering cost (presentation delay)
If handlers finish quickly but the screen still updates late, the browser is struggling to render.
- Shrink the DOM. Pages with many thousands of elements (mega-menus, long product grids, page-builder nesting) are slower to restyle and lay out on every interaction. Render menus and modals when opened rather than all upfront.
- Use
content-visibility: autoon long off-screen sections so the browser skips rendering them until needed:
.product-grid-section {
content-visibility: auto;
contain-intrinsic-size: auto 800px;
}
- Avoid generating large chunks of HTML with JavaScript in response to a click. Render the minimum needed for the next frame.
Step 5: Deal with third-party scripts
On many small-business sites, the biggest INP problem is not first-party code at all. Chat widgets, A/B testing tools, tag managers with dozens of tags, session recorders, social embeds and ad scripts all compete for the same main thread.
How to find them:
- In a DevTools performance trace, long tasks are labeled with the script that ran. Look for third-party domains.
- The Long Animation Frames API (Chrome 123+) attributes slow frames to specific scripts, including the source URL:
new PerformanceObserver((list) => {
for (const frame of list.getEntries()) {
for (const s of frame.scripts) {
if (s.duration > 50) console.log(Math.round(s.duration), 'ms', s.sourceURL, s.invoker);
}
}
}).observe({ type: 'long-animation-frame', buffered: true });
What to do:
- Audit your tag manager. Remove tags for tools nobody uses any more. This is often the single biggest win.
- Load widgets on interaction. Show a static "Chat with us" button and load the chat script only when clicked. The same "facade" approach works for YouTube embeds and maps.
- Delay non-critical tags until after the page has loaded, or until the first user interaction.
- Ask vendors about lighter versions or async loading options.
Quick reference
| Breakdown shows | Fix first |
|---|---|
| Large input delay | Cut and defer startup JS, audit third-party tags, remove heavy timers |
| Long processing | Yield after visible updates, debounce, avoid layout thrashing |
| Long presentation delay | Smaller DOM, content-visibility, render less on click |
Frequently asked questions
Why does PageSpeed Insights show good lab results but poor INP?
Lighthouse does not measure INP at all. It measures Total Blocking Time during load, with no clicks. Your real users interact throughout the visit, on slower devices, often while third-party scripts are still running, so field INP can be much worse than lab results suggest.
Is INP measured on every interaction or just the first?
Every click, tap and key press during the visit is observed. The reported value is close to the slowest one, with one high outlier ignored for every 50 interactions on interaction-heavy pages.
Can a WordPress plugin fix INP?
Some performance plugins can delay JavaScript until user interaction, which often reduces input delay during load. But they can also break features, and they do not fix slow event handlers or a bloated DOM. Test carefully and measure field INP before and after.
What is a good Total Blocking Time?
Lighthouse treats TBT as a lab proxy for responsiveness, and it carries the biggest weight in the performance score. Lower is better, but use it only as a hint; field INP is the number Google assesses.
What to do next
Collect INP attribution from real users for a week, pick the single slowest interaction, and check which phase dominates. Fix that phase, then move to the next interaction. For background on all three metrics see Core Web Vitals explained, and for script loading order see eliminating render-blocking resources. To track your performance score and Core Web Vitals over time, run a free audit or set up scheduled re-scans on a paid plan.
Sources
Written by the MakeItFlow team
We build the website audit these guides are based on. Every fact is checked against the primary sources listed above . How we research and check
Check your own site in 30 seconds
Run a free audit covering speed, SEO, DNS, email authentication and security headers — and get a prioritised fix list.