← Back to blog

Technical SEO · July 3, 2026 · 7 min read

INP Optimization Playbook: How to Fix Interaction to Next Paint Before It Tanks Your Rankings

Step-by-step INP optimization guide: diagnose input delay, fix heavy event handlers, and reduce presentation delay before rankings drop.

By FluxWriter Team

INP Optimization Playbook: How to Fix Interaction to Next Paint Before It Tanks Your Rankings

INP optimization is no longer optional—Google made Interaction to Next Paint a Core Web Vital in March 2024, replacing First Input Delay, and sites that ignore it are already seeing ranking pressure. Unlike FID, which only measured input delay, INP covers the full latency of every click, tap, and key press from the moment of interaction to the next frame paint. That broader scope means most sites that passed FID are failing INP.

This playbook skips the theory and goes straight to diagnosis and fixes.


Understanding What INP Actually Measures

INP captures three phases of every interaction:

  1. Input delay — time between the user's action and when the browser starts processing the event handler.
  2. Processing time — time spent running your JavaScript event handlers.
  3. Presentation delay — time from when handlers finish to when the frame actually paints.

A "good" INP score is under 200 ms. "Needs improvement" is 200–500 ms. Above 500 ms is poor. Google's field data shows the 75th percentile interaction on the page sets the INP score—not the worst single interaction, but the worst within a realistic distribution.

The most common culprits by phase:

Phase Common Cause
Input delay Long tasks blocking the main thread
Processing time Heavy event handlers, synchronous DOM reads
Presentation delay Forced layout/reflow, large render tree updates

Step 1: Diagnose in the Field First

Lab tools like Lighthouse run in a controlled environment with no real user behavior. INP almost always shows up in field data before lab tools catch it.

Sources to check:

import { onINP } from 'web-vitals/attribution';

onINP(({ value, attribution }) => {
  const { eventType, eventTarget, inputDelay, processingDuration, presentationDelay } = attribution;
  sendToAnalytics({ value, eventType, eventTarget, inputDelay, processingDuration, presentationDelay });
});

This attribution breakdown is the fastest way to identify which event type and which DOM element is causing your worst interactions. Group by eventTarget in your analytics to find the hottest offenders.


Step 2: Profile Long Tasks Causing Input Delay

Long tasks—JavaScript that runs for more than 50 ms without yielding—are the primary driver of input delay. While a long task is executing, the browser cannot process input. If a user clicks during a 300 ms task, that's 300 ms of input delay before the handler even starts.

How to find them:

  1. Open Chrome DevTools → Performance tab.
  2. Record a session while clicking the slow element.
  3. Look for red triangles at the top of the flame chart. These mark long tasks.
  4. Identify the function at the top of the call stack during the long task.

How to break them up:

Replace synchronous loops with scheduler yields:

// Before: blocks the main thread
function processItems(items) {
  for (const item of items) {
    expensiveOperation(item);
  }
}

// After: yields between chunks
async function processItems(items) {
  for (const item of items) {
    expensiveOperation(item);
    await scheduler.yield(); // releases control to the browser
  }
}

scheduler.yield() is now supported in Chrome 115+ and Edge. For broader support, fall back to setTimeout(resolve, 0) wrapped in a Promise.


Step 3: Slim Down Event Handlers

Even after clearing input delay, heavy event handlers inflate processing time. Two patterns cause most of the damage.

Synchronous DOM reads inside handlers

Mixing reads and writes forces the browser to recalculate layout mid-handler (layout thrashing). Each forced layout can cost 5–50 ms on a real device.

// Bad: read → write → read → write = multiple forced layouts
element.addEventListener('click', () => {
  const height = element.offsetHeight; // forces layout
  element.style.height = height + 10 + 'px'; // invalidates layout
  const newHeight = element.offsetHeight; // forces layout again
});

// Good: batch reads, then writes
element.addEventListener('click', () => {
  const height = element.offsetHeight; // one read
  // do all writes after
  element.style.height = height + 10 + 'px';
});

Third-party scripts injecting into handlers

Tag managers, analytics, and chat widgets often listen to every click on the page via event delegation. A single third-party listener that does synchronous work on every click can add 50–200 ms to processing time without any trace in your own code. Profile with third-party scripts disabled to isolate the delta.


Step 4: Reduce Presentation Delay

The last phase—presentation delay—is where many teams stop looking. Even if your handler completes in 20 ms, a large DOM update can delay the paint by another 300 ms.

Key causes:

Practical fix: defer non-critical visual updates

button.addEventListener('click', () => {
  // Critical: update state immediately
  updateButtonState(button);

  // Non-critical: defer to next frame
  requestAnimationFrame(() => {
    updateSidebar();
    refreshCounters();
  });
});

This keeps the critical feedback path fast while pushing secondary DOM work out of the initial paint.


Step 5: Target Mobile and Low-End Devices

INP scores in CrUX are heavily weighted toward mobile users on mid-range and low-end Android devices. A fix that works on a MacBook may be insufficient on a Moto G Power.

Use Chrome DevTools CPU throttling (6x slowdown) to simulate these conditions in the lab. If you're seeing INP > 200 ms at 6x throttle, your field data on real Android devices is likely worse.

For React and other virtual-DOM frameworks, a common fix is to move state updates into startTransition:

import { startTransition } from 'react';

function handleClick(value) {
  // Urgent: tell React the input changed now
  setInputValue(value);

  // Non-urgent: re-rendering the big list can wait
  startTransition(() => {
    setSearchResults(filterList(value));
  });
}

startTransition marks the re-render as non-urgent, letting React yield to the browser between work units and preventing the UI from blocking on a large list re-render.


Validating Fixes Before Deploying

Don't rely on a single Lighthouse run. Use this validation sequence:

  1. Lab test: Chrome DevTools Performance panel with 6x CPU throttle. Confirm no long tasks > 50 ms during interactions.
  2. Synthetic test: WebPageTest with a real Android device profile. Check the INP metric in the test results.
  3. Staging RUM: Deploy the web-vitals snippet to staging with interaction attribution. Generate a representative sample of clicks and verify processingDuration and inputDelay distributions.
  4. Canary rollout: Deploy to 5–10% of traffic, monitor CrUX via the API for 2–4 weeks (CrUX updates monthly, but the API has a 28-day rolling window).

FAQ

Does fixing INP directly improve Google rankings?

INP is a Core Web Vital used as a ranking signal via the Page Experience system. Google has confirmed CWVs are a tiebreaker signal—not a dominant ranking factor—but on competitive SERPs where other signals are close, a "poor" INP can cost positions. More reliably, a faster INP improves engagement metrics that indirectly affect ranking.

My INP is fine on desktop but poor in Search Console. Why?

CrUX aggregates field data from Chrome users across all devices. Most traffic on most sites skews toward mobile on mid-range hardware. Desktop is typically 3–5x faster at JavaScript execution than median Android devices. Check the Search Console CWV report with the "Phone" filter applied—that's where the problem likely lives.

How long does it take for INP fixes to show up in Search Console?

The CrUX dataset used by Search Console is a 28-day rolling window. After deploying a fix, expect 4–8 weeks before the Search Console report fully reflects the improvement, because old "poor" interactions need to age out of the window. You can track progress faster with the CrUX History API, which provides monthly snapshots going back further.


Practical Takeaway

Start with CrUX field data and web-vitals attribution to find the real offenders—don't guess. Break up long tasks with scheduler.yield(), batch DOM reads before writes, defer non-critical visual updates to the next frame, and validate with CPU throttling before declaring a fix done. INP problems compound on mobile; always profile on throttled conditions.

If you're managing a content site and want INP diagnostics surfaced alongside your other technical SEO signals without setting up separate tooling, FluxWriter includes Core Web Vital tracking in its site audit workflow—so slow interactions don't slip through unnoticed.



← All posts