Back to Blog
PerformanceJune 20, 20264 min read

INP explained and how to actually improve it in production

INP explained: learn how to measure, debug, and improve your Interaction to Next Paint scores to fix sluggish user experiences in production apps.

PerformanceFrontendWeb VitalsJavaScriptReactWeb Development
Organized whiteboard with colorful sticky notes used for planning and brainstorming.

I spent three days last month chasing a ghost in a React dashboard that felt "heavy" despite having decent scores on every other Core Web Vital. The culprit was a high Interaction to Next Paint (INP) score that only appeared under real-world conditions, proving that even with a solid React Performance Patterns You Actually Need in 2025 strategy, you can still hit a wall if you aren't watching the right metrics.

What is INP?

INP measures the latency of all interactions a user has with your page throughout its entire lifecycle. Unlike First Input Delay (FID), which only measures the very first click, INP looks at the worst (or near-worst) interaction. It captures the time from when the user clicks or taps until the browser actually paints the next frame.

Think of it as a three-part journey:

  1. Input Delay: The time between the user action and the event handler firing.
  2. Processing Time: The time it takes for your JavaScript to execute the event handler.
  3. Presentation Delay: The time the browser takes to calculate styles and layout to paint the update.

If your INP is above 200ms, your site feels sluggish. Above 500ms, it feels broken.

Why your first attempt will probably fail

Wooden tiles spell 'Fail Your Way to Success' emphasizing perseverance.

We initially tried to "fix" our INP by wrapping every event handler in useCallback and memo. It didn't work. In fact, it barely moved the needle because our issue wasn't component re-rendering—it was long-running JavaScript execution blocking the main thread.

We were triggering a heavy data-transformation function inside an onClick handler. Even if the component itself was memoized, the main thread was still locked for about 350ms while the browser processed the logic. We were trying to solve a execution-time problem with a rendering-time tool.

How to actually improve INP

To fix INP, you need to stop blocking the main thread. Here is the workflow I use now:

1. Identify the long tasks

You can't fix what you can't see. Open Chrome DevTools, go to the Performance tab, and record an interaction. Look for the red triangles in the top bar—these represent long tasks (anything over 50ms). If your interaction triggers a long task, that’s your bottleneck.

2. Break up long tasks

If you have a function that takes 200ms, you need to slice it. The easiest way is using scheduler.yield() or a simple setTimeout trick to let the browser breathe.

JAVASCRIPT
// Before: Blocking the main thread
const handleUpdate = () => {
  processData(); // Takes 200ms
  updateUI();
};

// After: Yielding to the browser
const handleUpdate = async () => {
  await new Promise(resolve => setTimeout(resolve, 0));
  processData(); 
  updateUI();
};

By yielding, you allow the browser to process pending paints, keeping the UI responsive.

3. Move heavy lifting off-thread

If you’re doing heavy calculations—like parsing large JSON objects or complex filtering—move that logic into a Web Worker. It’s a bit more boilerplate, but it completely removes that heavy computation from the main thread, making your INP scores drop significantly.

Beyond the numbers

Vibrant numbered grid on a concrete surface outdoors. Perfect for educational materials.

While chasing a "good" INP score is important, don't let it become a vanity metric. I’ve seen teams obsess over sub-100ms scores while ignoring the fact that their app's core functionality is still confusing. I usually find that when I focus on keeping the main thread clear, other things like Generics in TypeScript that actually pay off for your codebase start to matter more because the code becomes cleaner and easier to refactor without side effects.

FAQ

Does hydration affect INP? Yes. If your app is still hydrating when the user clicks, the main thread is likely busy, which will inflate your INP. Ensure your bundle size is lean so hydration finishes faster.

Is INP only about clicks? No, it includes keyboard interactions and taps. Anything that triggers a state change and a subsequent visual update counts.

What if my INP is fine but the app still feels slow? Check your CSS. Sometimes the "Presentation Delay" is high because of complex layout shifts or heavy paint-inducing CSS properties.

I’m still not 100% satisfied with our current approach to tracking INP in a local dev environment—it’s notoriously difficult to simulate real-world CPU throttling accurately. We’re currently exploring more robust RUM (Real User Monitoring) tools to get a better picture of what our users are actually seeing. If you're just starting, don't over-engineer the measurement tools; start by manually profiling the most common interactions and breaking up those long tasks.

Similar Posts