inp-analyser - find out why your page has poor INP

· inp-analyser

14 min read Original article ↗

INP · Core Web Vitals · Open source

Element, phase, script, owner, milliseconds, certainty.

inp-analyser is an open-source CLI that diagnoses Interaction to Next Paint, reconciled against real-user field data. An open-source tool by FullStack Optimization / Franz Enzenhofer.

  • 17Trace detectors
  • 95%Confidence intervals
  • MITLicence

Anatomy of one interaction

Example interaction, illustrative lengths, not a measurement: from the tap to the next paint in three phases, on a 0 to 300 ms scale with Google's 200 ms "good" threshold.

  1. 01 Input delay main thread busy

  2. 02 Processing event handlers run

  3. 03 Presentation style, layout, paint

Key facts

  • Field INP checked against PSI
  • 95% confidence intervals
  • Causal third-party tests
  • Diagnoses, never prescribes

01 / The metric

What INP is.Three phases per tap.

Interaction to Next Paint is the Core Web Vital for responsiveness. For every page visit, the browser measures how long each click, tap and key press takes until the next frame is painted; the page's INP is (close to) the slowest of them. Google reports the 75th percentile over 28 days of real Chrome users.

INP p75 thresholds from web.dev: good up to 200 ms, needs improvement up to 500 ms, poor above.

Every interaction has three phases, and each has different causes (Optimize INP):

  1. 01 Input delay

    The main thread is busy (often a long task) when the user taps.

    phase: input-delay
  2. 02 Processing duration

    The event handlers themselves run.

    phase: processing
  3. 03 Presentation delay

    Style, layout, paint and any script that squeezes in before the next frame.

    phase: presentation

Lighthouse and PSI give you a number. This tool gives you the reasons.

02 / What it finds

Six facts.Every single issue.

Every issue in the report carries the same set of facts. The examples are real output (bundled test site, a third-party script adding 200 ms per click; and Wikipedia):

01 / Elementbutton#apply-sort; on Wikipedia div#mwBA > a:nth-of-type(2)Discovered interaction target (CSS selector)

02 / Phaseprocessing (of input-delay, processing, presentation, context)Event Timing phases, split per interaction

03 / ScriptknownDeltaListener (http://cdn.thirdparty.test:63958/known-delta.js:4:65), 198 msJS samples in a Chrome trace: URL, line, function

04 / OwnerTest Tracker Inc. (3rd party); blocking it: 134 ms faster (95% CI 97-179 ms)third-party-web entity map, extendable

05 / Millisecondsmedian 222 ms (95% CI 221-223 ms)Exact order-statistic CI of the median

06 / Certaintyconfirmed, fired in 8 of 8 runsScreening vs deep-dive runs, Wilson interval

The headline is always the field number: the CrUX p75 that PageSpeed Insights and Search Console show. Lab values are evidence, labelled lab @ CPU Nx. It diagnoses; it never prints fixes.

03 / Examples: real runs

Real pages.Real findings.

Unedited runs of inp analyse on real pages: field data first, then what the lab found, with element, phase, script, owner, milliseconds and certainty. Every number below comes from the run files in examples/.

Example 01 / Wikipedia

/wiki/United_States

en.wikipedia.org - inp analyse, mobile, CPU 4x, seed 42, Chrome 155.0.8059.39, run 2026-11-11

Phone, URL level

103msgood (200 ms or less)

  • good 91.3%
  • needs improvement 6.7%
  • poor 2.1%

CrUX p75, 2026-10-12 to 2026-11-08

Desktop, URL level

110msgood (200 ms or less)

  • good 85.5%
  • needs improvement 8.3%
  • poor 6.2%

CrUX p75, 2026-10-12 to 2026-11-08

PSI parity: phone URL identical (103 ms); phone origin identical (87 ms); desktop URL identical (110 ms); desktop origin identical (49 ms).

Lab: 147 measured runs, 0 excluded, 151 page loads. 8 issues, 1 confirmed. 13 lab scenario(s) reach the field p75. Budget ended early: INCOMPLETE (time budget reached before deep-dive block 102).

Top findings

input-delay/boot-windowinput-delayconfirmed

Same element is slower when tapped while the page boots

Element
div#mwBA > a:nth-of-type(2)
Evidence
boot median: 277 ms
Evidence
idle median: 78 ms
Time
+199 ms (Hodges-Lehmann shift) (95% CI 116-265 ms)
Certainty
confirmed
Runs
fired in 9 of 9 runs (95% CI 70-100%)

context/off-main-thread-timecontextscreening-only

Most of a slow interaction is not main-thread work (compositor / GPU / frame wait)

Element
button#searchIcon
Evidence
not explained by main-thread work: 258 ms of 308 ms
Time
median 258 ms
Certainty
screening only
Runs
fired in 1 of 1 runs (95% CI 21-100%)

presentation/rendering-dominantpresentationscreening-only

Rendering work dominates a slow interaction (presentation delay is the largest phase)

Element
button#searchIcon
Evidence
style: 2 ms
Evidence
layout: 1 ms
Time
median 201 ms
Certainty
screening only
Runs
fired in 1 of 1 runs (95% CI 21-100%)

Showing 3 of 8 issues, confirmed first.

terminal.txt

Issues (8, ranked):
1. [needs improvement | confirmed | input delay] Same element is slower when tapped while the page boots
   div#mwBA > a:nth-of-type(2) - lab @ CPU 4x, mobile: effect 199 ms (Hodges-Lehmann shift) (95% CI 116-265 ms); fired in 9 of 9 runs (95% CI 70-100%)
   evidence: boot median: 277 ms
   why this counts: https://web.dev/articles/optimize-input-delay
2. [needs improvement | screening only | context] Most of a slow interaction is not main-thread work (compositor / GPU / frame wait)
   button#searchIcon - lab @ CPU 4x, mobile: median 258 ms; fired in 1 of 1 runs (95% CI 21-100%)
   evidence: not explained by main-thread work: 258 ms of 308 ms
   why this counts: https://web.dev/articles/inp#whats_in_an_interaction
3. [needs improvement | screening only | presentation delay] Rendering work dominates a slow interaction (presentation delay is the largest phase)
   button#searchIcon - lab @ CPU 4x, mobile: median 201 ms; fired in 1 of 1 runs (95% CI 21-100%)
   evidence: style: 2 ms
   why this counts: https://web.dev/articles/inp#good-score
4. [needs improvement | screening only | context] Large DOM (more than ~800 / ~1,400 elements, Lighthouse dom-size)
...
Run: 147 runs (0 excluded), 151 page loads, seed 42, Chrome/155.0.8059.39, lab @ CPU 4x, mobile; INCOMPLETE: time budget reached before deep-dive block 102

Full output: examples/wikipedia-united-states/

Example 02 / CNN

/2026/11/04/science/southern-taurids-meteor-shower

edition.cnn.com - inp analyse, mobile, CPU 4x, seed 42, Chrome 155.0.8059.39, run 2026-11-11

Phone, URL level

115msgood (200 ms or less)

  • good 88.1%
  • needs improvement 6.9%
  • poor 5.0%

CrUX p75, 2026-10-13 to 2026-11-09

Desktop, URL level

60msgood (200 ms or less)

  • good 96.4%
  • needs improvement 2.1%
  • poor 1.5%

CrUX p75, 2026-10-13 to 2026-11-09

PSI parity: phone URL identical (115 ms); phone origin identical (180 ms); desktop URL identical (60 ms); desktop origin identical (89 ms).

Lab: 18 measured runs, 0 excluded, 22 page loads. 3 issues, 0 confirmed. 0 lab scenario(s) reach the field p75. The report says: "No lab scenario reached it: not reproduced in the lab (real devices, logged-in states, iframes, untested interactions)."

Top findings

context/dom-sizecontextscreening-only

Large DOM (more than ~800 / ~1,400 elements, Lighthouse dom-size)

Evidence
DOM elements: 2178
Evidence
max depth: 24
Certainty
screening only
Runs
fired in 5 of 5 runs (95% CI 57-100%)

context/page-exceptionscontextscreening-only

Uncaught exceptions on the page during the run

Evidence
exception: Script error.
Certainty
screening only
Runs
fired in 5 of 5 runs (95% CI 57-100%)

presentation/non-composited-animationpresentationscreening-only

Non-composited animation runs during the interaction (needs layout/paint every frame)

Evidence
DIV id='onetrust-banner-sdk' class='otFlat ot-iab-2 bottom vertical-align-content ot-buttons-fw' visibility: failure code 8200; properties: visibility
Evidence
DIV class='onetrust-pc-dark-filter ot-fade-in' visibility: failure code 8200; properties: visibility
Certainty
screening only
Runs
fired in 1 of 1 runs (95% CI 21-100%)

terminal.txt

Issues (3, ranked):
1. [info | screening only | context] Large DOM (more than ~800 / ~1,400 elements, Lighthouse dom-size)
   lab @ CPU 4x, mobile: no duration attached; fired in 5 of 5 runs (95% CI 57-100%)
   evidence: DOM elements: 2178
   why this counts: https://developer.chrome.com/docs/lighthouse/performance/dom-size
2. [info | screening only | context] Uncaught exceptions on the page during the run
   lab @ CPU 4x, mobile: no duration attached; fired in 5 of 5 runs (95% CI 57-100%)
   evidence: exception: Script error.
   why this counts: https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event
3. [info | screening only | presentation delay] Non-composited animation runs during the interaction (needs layout/paint every frame)
   lab @ CPU 4x, mobile: no duration attached; fired in 1 of 1 runs (95% CI 21-100%)
   evidence: DIV id='onetrust-banner-sdk' class='otFlat ot-iab-2 bottom vertical-align-content ot-buttons-f...
   why this counts: https://developer.chrome.com/docs/lighthouse/performance/non-composited-animations
Run: 18 runs (0 excluded), 22 page loads, seed 42, Chrome/155.0.8059.39, lab @ CPU 4x, mobile

Full output: examples/cnn-southern-taurids/

Example 03 / TechCrunch

/2026/11/07/amazons-new-basic-kindle-proves-you-dont-need-all-the-bells-and-whistles/

techcrunch.com - inp field, fetched 2026-11-11

Phone, URL level

229msneeds improvement

  • good 69.8%
  • needs improvement 22.7%
  • poor 7.5%

CrUX p75, 2026-10-13 to 2026-11-09

Desktop, URL level

60msgood (200 ms or less)

  • good 94.0%
  • needs improvement 3.4%
  • poor 2.7%

CrUX p75, 2026-10-13 to 2026-11-09

PSI parity: phone URL identical (229 ms); phone origin identical (177 ms); desktop URL identical (60 ms); desktop origin identical (68 ms).

Lab: pending. The lab runs on 11 Nov 2026 did not finish: one page load timed out after 60 s, and the latest run met a Cloudflare challenge page. The tool stops at a challenge ([safety] bot wall) instead of measuring it or retrying, because a challenge page is not the page. So this card shows the real field data only, and no lab numbers.

inp field

phone  : INP p75 229 ms (url level, 2026-10-13..2026-11-09) good 69.8% / needs improvement 22.7% / poor 7.5% | PSI: equal
desktop: INP p75 60 ms (url level, 2026-10-13..2026-11-09) good 94.0% / needs improvement 3.4% / poor 2.7% | PSI: equal
  - CrUX API: p75 and good/NI/poor shares of real Chrome users over the 28-day collection period 2026-10-13..2026-11-09. https://developer.chrome.com/docs/crux/api
  - CrUX History API: 25 weekly 28-day periods, oldest first; ineligible periods are null. https://developer.chrome.com/docs/crux/history-api
  - PageSpeed Insights loadingExperience/originLoadingExperience (analysed 2026-11-11T11:20:53.717Z / 2026-11-11T11:20:53.665Z): separate source next to CrUX, reports no collection period. https://developers.google.com/speed/docs/insights/rest/v5/pagespeedapi/runpagespeed
  - CrUX history PHONE url https://techcrunch.com/2026/11/07/amazons-new-basic-kindle-proves-you-dont-need-all-the-bells-and-whistles/: 24 of 25 periods ineligible (kept as null).
  - CrUX history DESKTOP url https://techcrunch.com/2026/11/07/amazons-new-basic-kindle-proves-you-dont-need-all-the-bells-and-whistles/: 24 of 25 periods ineligible (kept as null).
  - CrUX PHONE vs PSI mobile, url level: identical (p75 229 ms, same good/NI/poor shares).
  - CrUX PHONE vs PSI mobile, origin level: identical (p75 177 ms, same good/NI/poor shares).
  - CrUX DESKTOP vs PSI desktop, url level: identical (p75 60 ms, same good/NI/poor shares).
  - CrUX DESKTOP vs PSI desktop, origin level: identical (p75 68 ms, same good/NI/poor shares).
[inp] field: examples/techcrunch-basic-kindle/field.json

Field data: examples/techcrunch-basic-kindle/

The tool also says what it does not know: on Wikipedia the search button's slow tap is mostly compositor / GPU time, so no script is blamed, and one-run findings stay screening only.

Ground truth: the bundled test site

The repository ships a test site where a third-party script adds exactly 200 ms to every click. The tool has to find it, name it and prove it (run of 11 Nov 2026, mobile, CPU 4x, seed 42, full output).

processing/slow-event-handlers processing confirmed

Bundled test site, page ab-known-delta: a third-party script adds 200 ms to every click. The tool names it.

Element
button#apply-sort
Script
knownDeltaListener (http://cdn.thirdparty.test:63958/known-delta.js:4:65) via #document.onclick, 198 ms
Owner
Test Tracker Inc. (3rd party)
Time
lab @ CPU 4x, mobile: median 222 ms, 95% CI [221, 223]
Certainty
confirmed
Runs
fired in 8 of 8 runs (95% CI 68-100%)

third-party/causal-delta context confirmed

Same page, proven by blocking: the tap gets faster without Test Tracker Inc. - cause, not just correlation.

Element
button#apply-sort
Owner
Test Tracker Inc. (3rd party), blocked vs allowed
Time
134 ms faster when blocked (Hodges-Lehmann shift, 95% CI 97-179 ms); with third parties 231 ms
Certainty
confirmed, paired blocks, Holm-corrected
Runs
28 of 28 paired runs

Field data in detail: /wiki/Main_Page

Field data for en.wikipedia.org/wiki/Main_Page, fetched with inp field on 11 Nov 2026, including 25 weeks of CrUX history.

130ms

good (200 ms or less)

URL level - CrUX, 28 days 2026-10-12 to 2026-11-08 - real Chrome users on phones

  • good 86.5%
  • needs improvement 9.7%
  • poor 3.9%

PSI parity: PageSpeed Insights shows the identical value - p75 130 ms and the same good / needs improvement / poor shares, at URL and origin level.

Desktop, URL level61 ms

  • good 96.8%
  • needs improvement 2.3%
  • poor 0.9%

Whole origin en.wikipedia.org87 ms phone / 49 ms desktop

Phone INP p75, 25 weekly CrUX periods

  • Green line with dots = INP p75 on phones, /wiki/Main_Page, URL level, one point per 28-day period (n = 25, weekly steps)
  • Grey lines = 5 ms gridlines. The y axis starts at 120 ms, not 0, so the small change stays visible.

Line chart of the CrUX History API phone INP p75 for en.wikipedia.org/wiki/Main_Page over 25 weekly 28-day periods, from the period ending 23 May 2026 to the period ending 7 Nov 2026. It starts at 137 ms, peaks at 139 ms in June, then falls steadily to 130 ms. All values are well inside the good range of 200 ms or less. 145 140 135 130 125 120 ms 23 May 20 Jun 18 Jul 15 Aug 12 Sep 10 Oct 7 Nov 28-day period ending (2026) 137 ms 139 ms peak 130 ms

Source: CrUX History API, phone, URL level, metric interaction_to_next_paint p75, fetched 11 Nov 2026 with inp field. Each point covers 28 days; consecutive points overlap by 21 days.

The same field data in the terminal

inp field

$ inp field https://en.wikipedia.org/wiki/Main_Page
phone  : INP p75 130 ms (url level, 2026-10-12..2026-11-08) good 86.5% / needs improvement 9.7% / poor 3.9% | PSI: equal
desktop: INP p75 61 ms (url level, 2026-10-12..2026-11-08) good 96.8% / needs improvement 2.3% / poor 0.9% | PSI: equal
  - CrUX PHONE vs PSI mobile, url level: identical (p75 130 ms, same good/NI/poor shares).
  - CrUX PHONE vs PSI mobile, origin level: identical (p75 87 ms, same good/NI/poor shares).
  - CrUX DESKTOP vs PSI desktop, url level: identical (p75 61 ms, same good/NI/poor shares).
  - CrUX DESKTOP vs PSI desktop, origin level: identical (p75 49 ms, same good/NI/poor shares).
[inp] field: inp-report-en.wikipedia.org-202611110531/field.json

Excerpt of the output fetched on 11 Nov 2026; the full output, with the source notes per API, is in the README.

04 / How it works

Seven stages.One verdict.

One pinned, headless Chrome. One page load at a time, in seeded random order. Every stage feeds the next, and the end result is checked against the real-user number.

  1. Field data. CrUX and PSI per device, URL and origin level, plus 25 weeks of history, fetched before any page load.
  2. Discovery + safety guard. Two passes keep only stable targets. Money, deletion, logout and form-submit actions are never clicked; mutating requests and navigations are blocked.
  3. Screening. Every target is tapped once the page is idle, and while it is still booting, in 3 interleaved blocks.
  4. Deep-dive attribution. Every scenario slower than 200 ms gets timing blocks until the median CI is narrow, then 5 traced attribution runs.
  5. Third-party causal blocks. Paired runs with third parties allowed and blocked prove which owner costs how many milliseconds, or flag the result as confounded.
  6. Rules + statistics. 17 trace-based detectors, exact median CIs, Wilson intervals, Hodges-Lehmann shifts, Mann-Whitney with Holm correction.
  7. Report. Issues, comparisons and limitations, with a verdict on whether the lab reaches the field p75.

Every block is health-gated: if the machine drifts, the block is kept in the raw data but excluded from all statistics. The tool refuses to run on battery, in Low Power Mode or under thermal throttling.

05 / Quick start

Clone it.Run it on any URL.

Requirements: Node.js 26 or newer, git, and about 500 MB of disk for the pinned Chrome for Testing build. Runs on macOS and Linux.

git clone https://github.com/franzenzenhofer/inp-analyser
cd inp-analyser
npm ci
npm run chrome:install
export INP_GOOGLE_API_KEY=...   # optional: field data from CrUX + PSI
npx tsx src/cli.ts analyse https://en.wikipedia.org/wiki/Main_Page --quick

The key needs the Chrome UX Report API and the PageSpeed Insights API enabled. No key yet? Add --no-field for a lab-only run. --quick stops after screening (typically 20 to 40 minutes); drop it for the full analysis. Then open report.html in the run directory.

Commands

inp field <url>
CrUX + PSI field INP for phone and desktop. No page loads. Writes field.json.

inp discover <url>
Lists the interaction targets analyse would measure (two passes, stable targets only), their safety class, excluded targets with the reason, and global third-party listeners.

inp analyse <url>
The full pipeline: preflight, discovery, screening, deep-dive, third-party causal tests, diagnosis, report.

inp report <dir>
Rebuilds report.json and report.html from a run directory (runs.jsonl + meta.json).

inp trace <file>
Issues in an existing DevTools Performance recording (.json or .json.gz). One recording is one sample: no statistics.

inp compare <before> <after>
Before/after on two run directories: Hodges-Lehmann shift + 95% CI, Mann-Whitney p (Holm-corrected), Cliff's delta per shared scenario.

inp calibrate
Measures Lighthouse BenchmarkIndex under each CPU throttling rate on this machine and names the device class each rate reaches.

All commands, options and the full methodology: README on GitHub.

06 / Agent skill

Ask your agent.It knows the rules.

The repository ships an agent skill for Claude Code and other agents that read the skill format. It teaches the agent when to use the tool, how to read report.json, and to never present a lab number as the INP.

cp -R skills/inp-analyser ~/.claude/skills/

Then just ask:

  • "Why is INP poor on https://example.com/?"
  • "What INP does PSI show for https://en.wikipedia.org/wiki/Main_Page, phone and desktop?"
  • "Analyse this DevTools trace for slow interactions."

Open source · MIT licence

An open-source toolby FullStack Optimization.