Bilal Gokdag
Back to Blog

Optimizing INP (Interaction to Next Paint) in React Applications

What is INP, the new standard Core Web Vitals metric, and how do you fix the main-thread bottlenecks that React's rendering model tends to cause?

Optimizing INP (Interaction to Next Paint) in React Applications

In March 2024, Google made a landmark change to the Core Web Vitals metrics, retiring FID (First Input Delay) in favor of INP (Interaction to Next Paint). Search rankings now measure how responsive a page is throughout its entire lifecycle, not just on first load.

As a senior frontend engineer, INP optimization needs to be part of your everyday routine on high-traffic platforms — especially interaction-heavy e-commerce sites.

What Is INP, and How Does It Differ from FID?

  • FID: Only measured the user's first interaction with the page. It accounted for wait time, not processing time.
  • INP: Tracks all of the user's interactions with the page (clicks, keyboard input, etc.). It measures the total time from the start of an interaction to the next paint being drawn on screen (delay + processing + render). The target value is 200 milliseconds or less.

Why Does INP Run High in React Apps?

By default, React works synchronously. When state updates, React computes the virtual DOM and applies it to the real DOM. If this work takes too long (a "long task"), it blocks the browser's main thread. Once the main thread is blocked, the browser can't respond to new clicks or scroll gestures — the site feels "frozen."

Strategy 1: Prioritizing Updates with startTransition and useDeferredValue

React 18's Concurrent features are our biggest weapon for INP optimization. If you're running a heavy filter or search operation, you can defer the non-urgent updates.

import { useState, useTransition } from "react";

export function ProductSearch() {
  const [query, setQuery] = useState("");
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    // 1. The user's keystroke is urgent — reflect it immediately (protects the INP score)
    setQuery(e.target.value);

    // 2. The heavy filtering work isn't urgent — queue it in the background
    startTransition(() => {
      performHeavyFiltering(e.target.value);
    });
  };

  return (
    <div>
      <input type="text" value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <ProductList query={query} />}
    </div>
  );
}

Need this kind of work done on your project?

I build production-ready, high-performance web applications — let's talk about what you're building.

Start a Project