Skip to main content
SEO & Marketing

Fixing INP: A Developer's Guide to Faster Interactions and Better Rankings

Interaction to Next Paint (INP) measures how quickly your site responds to taps, clicks and key presses. Here's how to find slow interactions and fix them in React and Next.js apps.

Ahsan Bilal

Principal Software Engineer

5 min read
On this page

Your page loads fast, scores well in Lighthouse — and still feels sluggish when people tap a button or open a menu. That gap is what Interaction to Next Paint (INP) measures. It replaced First Input Delay as a Core Web Vital in 2024, and it's the metric many otherwise-fast React sites struggle with.

What INP measures

INP looks at the interactions a user makes during a visit — clicks, taps and key presses — and reports one of the slowest: the time from the interaction until the browser paints the next frame. Google's thresholds, measured at the 75th percentile of real visits:

INPRating
200 ms or lessGood
200–500 msNeeds improvement
Over 500 msPoor

Every interaction has three phases, and any of them can be the problem:

  1. Input delay — the main thread is busy with something else when the user taps.
  2. Processing time — your event handlers (and the React re-render they trigger) run.
  3. Presentation delay — the browser recalculates styles, lays out and paints.

Step 1: Measure real users, not just lab tests

Lighthouse can't fully measure INP because it doesn't interact like a user. Start with field data:

  • Search Console → Core Web Vitals shows which URL groups fail on mobile and desktop.
  • PageSpeed Insights shows field data for a URL when enough visits exist.
  • Your own RUM: the web-vitals library reports INP with attribution — which element, which phase, which script.
ts
import { onINP } from "web-vitals/attribution";

onINP(({ value, attribution }) => {
  sendToAnalytics({
    metric: "INP",
    value,
    target: attribution.interactionTarget,
    inputDelay: attribution.inputDelay,
    processing: attribution.processingDuration,
    presentation: attribution.presentationDelay,
  });
});

In Next.js, useReportWebVitals from next/web-vitals gives you the same data. Then reproduce the slow interaction locally in the Chrome DevTools Performance panel with CPU throttling on — mid-range phones are much slower than your laptop.

Step 2: Reduce input delay

Input delay means something else was hogging the main thread. Common culprits:

  • Hydration of large component trees right after load. Keep client components small; render static content as Server Components so it never hydrates.
  • Third-party scripts — chat widgets, tag managers, heatmaps. Load them after the page is interactive, or move them off the main thread.
  • Long tasks from timers, analytics batching or large JSON parsing.

Break long tasks into smaller ones so the browser can respond in between. scheduler.yield() is available in Chromium-based browsers; fall back to a timeout elsewhere:

ts
const yieldToMain = () =>
  "scheduler" in globalThis && "yield" in (globalThis as any).scheduler
    ? (globalThis as any).scheduler.yield()
    : new Promise((resolve) => setTimeout(resolve, 0));

for (const batch of chunks(items, 50)) {
  process(batch);
  await yieldToMain();
}

Step 3: Make handlers and re-renders cheaper

Processing time is usually your re-render, not the handler itself.

  • Update the urgent thing first. Show the pressed state, open the menu, echo the keystroke — then defer expensive work with startTransition or useDeferredValue so React can paint in between.
  • Narrow state. State held high in the tree re-renders everything below it. Move it closer to where it's used, or split contexts.
  • Virtualize long lists instead of rendering thousands of rows.
  • Avoid layout thrashing — reading layout (getBoundingClientRect) after writing styles in the same handler forces synchronous layout.
  • The React Compiler can remove much manual memoization work; profile before and after rather than sprinkling useMemo everywhere.
tsx
function Search({ items }) {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query); // typing stays instant
  const results = useMemo(() => filter(items, deferredQuery), [items, deferredQuery]);
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <ResultList results={results} />
    </>
  );
}

Step 4: Cut presentation delay

After your code runs, the browser still has to render. Large DOMs and expensive CSS make that slow:

  • Keep the DOM lean — thousands of hidden nodes in menus and tabs still cost style and layout time.
  • Use content-visibility: auto for long off-screen sections.
  • Animate transform and opacity, not width, top or box-shadow.
  • Be careful with heavy backdrop-filter and large blurred elements on low-end devices.

Step 5: Watch the usual suspects in modern stacks

  • 3D and canvas scenes running every frame can starve interactions — pause them when off-screen and lower quality on weak devices.
  • Smooth-scroll libraries run code on every frame; make sure they're not fighting with your handlers.
  • Tag managers accumulate tags over the years; audit and remove the ones nobody uses.

Why it matters beyond rankings

Core Web Vitals are part of Google's page experience signals, but the bigger win is commercial: a form that responds instantly, a menu that opens on the first tap and a checkout that never feels stuck convert better. Fix INP for your users; the search benefits follow.

_Want a performance audit with fixes implemented, not just a report? See our SEO service and web development._

#Performance#Core Web Vitals#SEO#React#Next.js
All articles →
AI & Agents••5 min read

SEO for AI Search: How to Get Your Brand Cited in AI Answers

AI Overviews and chat assistants now answer many searches directly. Here's how technical SEO, structured data and genuinely useful content help your pages get cited — without chasing gimmicks.

SEOAI Search