How to fix high INP (over 200ms)
High INP — taps and clicks taking more than 200ms to respond — almost always comes from one cause: too much JavaScript running on the main thread. The fix is not a single change; it's a disciplined audit of what's blocking.
1. Find the slow interactions
Install the web-vitals library on your site and log INP events to your analytics:
import {onINP} from 'web-vitals';
onINP((metric) => {
// send to analytics with metric.entries[0].target (the element that was interacted with)
});
After a week you'll know exactly which buttons / links / inputs are the slowest. You can't fix what you can't see.
2. Break up long tasks
Any task over 50ms on the main thread blocks input. In an event handler, wrap expensive work in scheduler.yield() (or the postTask polyfill) between chunks:
async function handleFilterChange() {
applyFiltersQuickly();
await scheduler.yield();
renderFilteredListInBackground();
}
This breaks one 400ms task into two 200ms tasks with a yield in between — the browser can process input between them.
3. Audit third-party JavaScript
The single biggest INP killer is third-party scripts. Run Chrome DevTools → Performance → record a click → look at what's running.
Typical offenders:
- Google Tag Manager with 20 tags loaded
- Chat widgets (Intercom, Zendesk) doing polling work
- A/B test SDKs running experiment logic on every click
- Ad networks running auction logic
The fix: be ruthless. Every third-party script must justify its INP cost. Defer, lazy-load (load only after first interaction), or remove.
4. Reduce React re-renders
In React apps, a common INP pattern is: user types → state updates → entire tree re-renders → 600ms INP.
useMemo/useCallbackwhere the profiler shows wasted rendersReact.memoon expensive components- Split context so a change in A doesn't re-render B
- Virtualize long lists with
react-windowor@tanstack/react-virtual - Move derived state to
useMemowith a stable dependency array
5. Use content-visibility: auto for long pages
For pages with long stacks of sections (docs, feed pages, etc.), apply content-visibility: auto on below-the-fold sections. The browser skips layout and paint for off-screen content until needed.
6. Move heavy computation to a worker
If a click triggers real CPU work (image processing, markdown parsing, search indexing) — put it in a Web Worker. The main thread stays free for input.
7. Measure and iterate
INP is a p75 metric, so you're chasing the worst typical user. After every fix, re-deploy, wait a week, check p75. If p75 dropped, you're winning. If not, the bottleneck is elsewhere — go back to step 1 and find the new worst interaction.
A note on FID
If you still see FID-only in old articles and dashboards: FID was retired in March 2024. It's INP now. Dashboards that still show FID are stale.
By Paulo de Vries · Published