Core Web Vitals

INP: Finding the Slow Interaction in Field Data

INP fails in Search Console and the lab shows nothing. How to instrument real users to find which element, event and script is slow, and report it usefully.

5 min read
Core Web VitalsINPPerformance
924 words5 min read

The metric that hides

Interaction to Next Paint measures how long the page takes to visually respond after a tap, click or key press, and reports roughly the worst interaction a visitor experienced. Google's threshold is 200 milliseconds. Search Console tells you a page group is failing it. It does not tell you which interaction, and Lighthouse cannot, because Lighthouse does not interact with your page.

So you have a failing metric, no reproduction, and a page full of things a visitor might tap. The way out is to ask the visitors' browsers what was slow, and they will tell you, precisely.

What INP is made of

An interaction's latency has three parts:

  • Input delay. Time from the tap until the event handler starts. Long when the main thread is busy with something else, a long task from a script, a hydration pass, an ad.
  • Processing time. How long the handlers take. Long when the handler does heavy work synchronously: filtering a large list, a big state update that re-renders a lot of React, a synchronous layout read.
  • Presentation delay. Time from handlers finishing until the next frame paints. Long when the handler triggered an expensive re-render or layout, or a huge DOM change.

Knowing which part dominates tells you what kind of fix you need. Field attribution gives you all three.

Instrumenting with web-vitals attribution

The web-vitals library has an attribution build that reports, for the INP interaction, the element, the event type, the three timing parts, and (in recent versions) the longest script involved via the Long Animation Frames API.

TypeScript
// lib/vitals.ts
import { onINP } from 'web-vitals/attribution'

onINP(({ value, rating, attribution }) => {
  const a = attribution
  const payload = {
    metric: 'INP',
    value: Math.round(value),
    rating,
    target: a.interactionTarget,          // CSS selector of the element
    type: a.interactionType,              // 'pointer' | 'keyboard'
    inputDelay: Math.round(a.inputDelay),
    processing: Math.round(a.processingDuration),
    presentation: Math.round(a.presentationDelay),
    loadState: a.loadState,               // e.g. 'complete' or 'dom-interactive'
    script: a.longAnimationFrameEntries?.[0]?.scripts?.[0]?.sourceURL,
    page: location.pathname,
  }
  navigator.sendBeacon('/api/vitals', JSON.stringify(payload))
}, { reportAllChanges: false })

Load this from a Client Component on every page. Send it to your own endpoint, or to your analytics as a custom event, or to a RUM product that supports custom attributes. The volume is one event per page view at most.

Ad blockers block many third-party analytics endpoints; a first-party /api/vitals route on your own domain gets through, which matters because the visitors on blocked browsers are also real visitors.

Reading the data

After a few days of traffic, aggregate by target and page. You are looking for the elements that appear most often in INP reports rated poor, and for each, which timing component is largest.

Typical findings on business sites:

  • The mobile menu button with high input delay during load. A large script (tag manager, chat widget, framework hydration) is running when the visitor taps. Fix: defer that script; hydrate the menu first or make it work without JavaScript.
  • A search or filter input with high processing time. The keystroke handler filters and re-renders a large list synchronously. Fix: debounce, virtualise the list, or move filtering into a transition (startTransition) so the keystroke paints first.
  • An accordion or tab with high presentation delay. Opening it triggers layout of a large hidden section or loads images. Fix: reserve space, lazy-mount content, avoid layout thrash.
  • A cookie banner's Accept button with input delay, because accepting fires the consent-gated tag manager and everything loads at once. Fix: yield before loading tags; load them in chunks.
  • A "Book now" that opens a third-party widget with everything slow, because the widget's script is fetched and executed on click. Fix: preload the widget's script on hover or idle, and show an immediate visual response before the widget arrives.

The script field, when present, names the file. Often it is not yours.

Reproducing in the lab, once you know where

With a target element and a page, the lab becomes useful. In DevTools' Performance panel with CPU throttled 4x to 6x: record, perform the interaction, stop. The interactions track shows the input delay, processing and presentation as a bar; the main thread flame chart underneath shows what was running. Now you are looking at the actual slow thing rather than guessing.

Fixing by component

  • Input delay: break up long tasks (scheduler.yield(), setTimeout chunks), defer third-party scripts, reduce hydration cost by making less of the page a Client Component.
  • Processing: do less in the handler; move work into startTransition or after a requestAnimationFrame; debounce input; avoid synchronous layout reads (offsetHeight in a loop).
  • Presentation: smaller DOM updates; content-visibility: auto on large off-screen sections; avoid animating layout properties; virtualise long lists.

Give the visitor immediate visual feedback (a pressed state, a spinner) synchronously and cheaply, then do the heavy work. INP measures the next paint, not the completion of the task.

Verifying

The field data is the judge. Your own endpoint shows the distribution shifting within days; Search Console follows over 28 days. Keep the instrumentation running; INP regresses when a new script is added, and the report tells you which button got slow and when.

Where this fits

INP is the Core Web Vital most often failing on otherwise-fast business sites, and the one the lab is least able to diagnose. Field attribution is the first thing we set up in a Core Web Vitals engagement where INP is the problem, because a week of real data replaces a month of guessing which of forty buttons to look at.

All writingHire me for this