deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Lab tools cannot measure INP: what the Event Timing API really reports

A dev.to post explains why INP shows up blank in lab-only tests like Lighthouse, and why a basic Event Timing API observer often logs nothing even on pages people actively use.

Lab tools cannot measure INP: what the Event Timing API really reports

The blank is the metric working as designed

A post on dev.to walks through a confusion that surfaces constantly in Core Web Vitals reporting: Interaction to Next Paint (INP), one of the three Core Web Vitals, comes back blank in lab tests while every other metric reports normally.

The author runs a Core Web Vitals checker and pointed it at his own site, whattofixfirst.com, on 29 September 2026, using a simulated mid-range phone on throttled 4G. Largest Contentful Paint, Cumulative Layout Shift, Time to First Byte, First Contentful Paint and Total Blocking Time all returned values, and Lighthouse scored the page 89 out of 100. INP alone read "not measured."

According to the post, this is not a bug in Lighthouse or in any lab tool. Google's documentation states that a page produces no INP when the visitor never clicks, taps or presses a key, when they only scroll or hover, or when the page is loaded by a bot or a headless browser that has not been scripted to interact. Every lab run fits that last case: Lighthouse loads the page without touching it, so there is no interaction latency to observe. The only input types INP recognises are mouse clicks, touchscreen taps and key presses.

TBT is a proxy, not a replacement

The standard advice is to read Total Blocking Time instead. Google frames TBT as a possible proxy but explicitly not a stand-in, and the thresholds explain why. INP is assessed at the 75th percentile of page loads in the field, with good at 200 ms or less, needs improvement above 200 ms up to 500 ms, and poor above 500 ms. TBT only measures main-thread blocking during page load. A page can score well on TBT and then stall the first time a visitor opens a menu. In the author's own run, TBT was a comfortable 78 ms, which says almost nothing about what happens when someone types in a search box.

Field data would close the gap, but the checker reported that Google had insufficient real-visitor data for the origin, which the post notes is the normal state for small sites. Blank in the lab, blank in the field.

Why a naive Event Timing observer logs nothing

To collect the number yourself, you use the Event Timing API with a PerformanceObserver. The post identifies three reasons a working observer still prints nothing on a responsive page, all written into the W3C Working Draft dated 19 March 2026:

  • The default duration threshold is 104 ms. The spec chose this because it is the first multiple of 8 greater than 100 ms, so by default the API only surfaces slow interactions. A page where everything responds in 90 ms yields an empty log that looks identical to a broken observer.
  • Durations are rounded to multiples of 8 ms, a granularity the spec says keeps timing precise even on 120Hz displays. Comparing two interactions that differ by 5 ms is comparing noise.
  • The threshold can be lowered, but never below 16 ms. The spec reasons that on a 120Hz display, a response skipping more than a single frame takes at least 16 ms, and anything faster is not worth recording.

There is one exception: first-input entries are reported and buffered regardless of any threshold. Google's guidance, cited in the post, is to observe first-input alongside event so that consistently fast pages still produce a value. The corrected collector in the post sets durationThreshold to 16 and keeps only entries carrying an interactionId.

Where hand-rolled collection diverges from Google's numbers

Even with that fixed, a custom collector disagrees with what Google records in four documented ways:

  • INP is approximately the 98th percentile across all interactions on the page, computed at unload, not simply the worst one. The documented shortcut is to keep only the worst ten interactions in memory.
  • A page restored from the back/forward cache resets INP to zero, because the user experiences that as a separate visit. A collector that keeps accumulating across a restore reports a number nobody actually lived through.
  • Backgrounded tabs often never run unload callbacks on mobile, so the recommended fix is to report on visibilitychange and compute the final value server side.
  • The API does not surface event entries from inside iframes, but the metric counts them, since a visitor clicking play on an embedded video never knows a frame boundary was crossed. Subframes have to post their entries up to the parent, a known source of disagreement with CrUX data.

That list, the author concludes, is why the practical answer for most sites is the onINP helper from the web-vitals library rather than a hand-built collector.

Why it matters

The gap between lab and field for INP is definitional, not a tooling defect. Teams that optimise purely from Lighthouse runs get no signal at all on responsiveness, and small sites without CrUX data can be blind in both directions. Just as importantly, an empty Event Timing log usually means the page is fast, not that the observer is broken, and the rounding and threshold rules mean small duration differences are meaningless. Anyone who needs the real number has to collect it from actual visitors, and doing that correctly means handling percentiles, bfcache restores, background tabs and iframes, which is precisely the work the web-vitals library has already done.

  • #core-web-vitals
  • #inp
  • #event-timing-api
  • #web-performance
  • #lighthouse

Related posts