Web Vitals Optimization Handbook with an INP Focus
From field measurement to breaking long tasks, cutting main-thread work, and improving interaction responsiveness in React/Next apps.
Ali Mortazavi
Founder, Paradise Code
INP replaced FID because it measures real interaction
INP looks at response delay across the page lifetime, not only the first click. Users can see a good LCP and then freeze the UI opening a filter or menu—where experience and conversion drop. Optimizing “initial load” without interaction responsiveness is incomplete.
Engineering target: keep field p75 INP under the “good” threshold, especially on mid-tier mobile CPUs.
Measure in the field before optimizing the lab
Lighthouse helps but is not enough. RUM with attribution—which event, which element, which long task—shows the real path. A lab-slow page can be field-fast or the reverse; decide with CrUX/RUM.
Give each money path its own vitals dashboard. Site-wide averages hide checkout pain.
Break long tasks on the main thread
Interactions die when the main thread is busy with hydration, big parses, or sync computation. Slice the work: `scheduler.yield`/zero timeouts, chunked list processing, and Workers when possible. In React, avoid hydrating the whole tree in one frame.
Load heavy libraries behind user intent—editors, maps, charts—not in the initial bundle. Dynamic import must match waiting UX, not just code-split mechanics.
Input, handlers, and UI updates
Handlers should be light: minimal validation, immediate optimistic UI, then network/heavy work. Debounce hot inputs and avoid wide re-renders from state placed too high in the tree. If one click shakes three contexts, your state boundary is wrong.
For large lists, virtualization and stable keys are mandatory. Keep animation on `transform`/`opacity` to avoid layout thrash.
Do not break LCP and CLS while fixing INP
Delaying the hero image to help INP can sacrifice LCP. Prioritize resources explicitly: `fetchpriority` for the LCP image, preload critical fonts with reserved space to prevent CLS, and size reservations for banners and buttons.
Fonts and third-party ads are classic CLS killers. If a third party is required, load it in a fixed-size container after idle or consent.
JavaScript budgets and team culture
Set KB and long-task budgets on key routes and fail CI when they breach. Without a ceiling, every feature adds “just one library” until INP dies. A budget needs an owner—like a product owner for the path.
Performance work is not a cleanup project; it is part of feature design. If a new interaction introduces a new long task, the same PR should pay for it.
Frequently asked questions
What is a good INP threshold?
Under current Web Vitals guidance, p75 under 200ms is good, up to 500ms needs improvement, and higher is poor. Track mobile separately.
Does shrinking the bundle always fix INP?
It often helps, but heavy handlers and wide re-renders also destroy INP. Read attribution, not only bundle size.
Do React Server Components help INP?
By cutting client JS and hydration, yes—if Client boundaries do not inflate again.
How should we manage third parties?
Do not load them unconstrained via a tag manager. Every script needs an owner, trigger, and budget; load non-essential ones after idle or consent.
Insights
Need these ideas implemented in your product?
Paradise Code supports you from consult to full delivery.