Interaction to Next Paint in the poor range
What is this issue?
Interaction to Next Paint (INP) measures how long the page takes to show a visible response after somebody taps, clicks or presses a key. It is not the time to finish the work -- it is the time until the screen changes at all, so the visitor can see that the page heard them.
Google puts every page into one of three bands:
| INP | Verdict |
|---|---|
| 200 ms or less | Good |
| 200 to 500 ms | Needs improvement |
| more than 500 ms | Poor |
This check reports pages in the poor band.
INP replaced First Input Delay as a Core Web Vital in March 2024, and it is a harsher measure: FID timed only the FIRST interaction and only up to the moment processing began, while INP looks at the whole visit and waits for the next frame to be painted.
INP can only be measured from real visitors. There is no interaction to time in
a synthetic lab run, so this check is raised only for pages the Chrome UX Report has
enough traffic for. A page with no field data has no INP, and PixyScan reports it as
Not measured rather than guessing.
Why it matters
- It is a ranking signal. INP is one of the three Core Web Vitals that feed Google's page-experience signals, and the one Search Console will report against this page.
- It is the metric users describe as "the site is broken". A visitor who taps a button and sees nothing for half a second taps again. Double submissions, rage clicks and abandoned forms are what a poor INP looks like from the outside.
- It is measured across the whole visit, not just the first tap, so it catches the menu that jams on the third open and the filter that freezes once the list is long -- the interactions a manual test rarely reaches.
- It is almost always JavaScript. Unlike LCP, INP is rarely about images or the network; it is about long tasks blocking the main thread, which means the fix is usually within your own code rather than in infrastructure.
Fixing it moves the page out of the poor band and removes an important-severity finding from the site's health score.
How to fix it
Find the interaction that is slow. INP reports the worst one on the page, not an average. Reproduce the page and try the menu, the filters, the accordion and the form -- one of them is doing far more work than the others.
Break up long tasks. Any single piece of JavaScript that runs for more than 50 ms blocks every input until it finishes. Split the work and yield to the browser between chunks so a tap can be handled in the gap.
Paint first, then do the work. Update the visible state immediately -- open the panel, check the box, show the spinner -- and defer filtering, sorting, analytics and network calls until after the next frame.
Do less on every keystroke and every scroll. Debounce handlers that fire continuously, and never do layout-reading work inside them.
Ship less JavaScript. Code that is not downloaded cannot block the main thread. Split bundles by route, and load third-party widgets -- chat, consent, A/B testing, tag managers -- after the page is interactive.
Avoid layout thrashing. Reading a layout property and then writing a style in the same handler forces a synchronous reflow, and doing it in a loop multiplies it by the number of elements.
Re-measure with real users. INP comes from real visitors, so a lab run cannot confirm the fix. Collect it in the field, or wait for the Chrome UX Report window to catch up.
Examples
1. Every keystroke re-filtered a thousand rows
A search box filtered the whole product list on input, synchronously, on every
character typed.
Fails -- INP 780 ms
input.addEventListener("input", (event) => {
const query = event.target.value;
render(products.filter((p) => matches(p, query))); // 1 200 rows, every keypress
});Passes -- INP 90 ms
let pending;
input.addEventListener("input", (event) => {
const query = event.target.value;
clearTimeout(pending);
pending = setTimeout(() => {
render(products.filter((p) => matches(p, query)));
}, 120);
});The visible caret still moves instantly; only the expensive work waits.
2. The work ran before the panel opened
An accordion sent an analytics event, measured three elements and then set the open state, so nothing appeared on screen until all of it had finished.
Fails -- INP 610 ms
button.addEventListener("click", () => {
track("accordion_open");
recalculateLayout();
panel.hidden = false;
});Passes -- INP 120 ms
button.addEventListener("click", () => {
panel.hidden = false; // paint first
requestIdleCallback(() => {
track("accordion_open");
recalculateLayout();
});
});3. No INP at all
A documentation page on a small site is sampled, and PageSpeed returns a Lighthouse run but no Chrome UX Report data for the URL.
Reported as Not measured -- and no finding is raised.
That is the correct outcome, not a gap. INP exists only where real people interacted, and there is no honest way to estimate it from a synthetic load.
How PixyScan detects this
Chooses which pages to measure. After the crawl, PixyScan measures the homepage plus the pages your own site links to most, up to a per-scan budget (ten pages by default).
Asks the PageSpeed Insights API about each of them, on mobile.
Uses real-user data only. INP comes from the Chrome UX Report -- the 75th percentile of what real Chrome users experienced on that exact URL over the last 28 days. There is no lab equivalent, because a synthetic page load contains no interaction to time.
Ignores origin-level data. When the report has too little data for the URL, PageSpeed answers with the whole site's distribution. That is a statement about the site, not this page, so it is not used here.
Raises the issue only above 500 ms -- Google's own "poor" bound. A page at exactly 500 ms is "needs improvement" and raises nothing.
Raises nothing without field data. A page the Chrome UX Report has never had enough traffic for has no INP at all. PixyScan reports
Not measuredfor it and this check cannot fire -- which is why an INP figure is rarer in the report than an LCP one, and why its absence is not a fault.
What we store
Storage Level
Page Level
Database Table / Prisma Model
PagePerformance (page_performance), with the finding itself on
audit_issues.details.
Fields Used
| Field | Type | Description |
|---|---|---|
| strategy | String | mobile or desktop. Every number in the row belongs to one form factor. |
| fetchedAt | DateTime | When PageSpeed answered. "Measured three weeks ago" is not "not measured". |
| errorReason | String? | Why a sampled page still has no numbers. Null when the call succeeded. |
| performanceScore | Int? | Lighthouse's performance category, rescaled 0-100. |
| fieldInpMs | Int? | INP from the Chrome UX Report for this URL. Null when the report has no data for it -- which is the usual case, and not a fault. |
On the finding (audit_issues.details): metric, source (field or lab),
valueMs and thresholdMs (500).
Detection Dependencies
- Chrome UX Report field data (
loadingExperience), which is the ONLY source of an INP -- Lighthouse cannot measure an interaction nobody made - PageSpeed Insights API v5, which carries that report
- The crawl's internal link graph, which decides which pages are sampled
Note
A page with no row was never measured. The pass samples the homepage plus the
most-linked pages within a per-scan budget (PERFORMANCE_SAMPLE_LIMIT, default 10),
so most pages of a site have no page_performance row at all. That is the normal
state, it is drawn as Not measured, and this check cannot be raised against it.
A sampled page can also have no INP because the Chrome UX Report has too little traffic for that URL. There is no lab fallback for this metric, so that is the most common reason an INP is missing -- and it is not a fault, and not a pass.