Skip to main content
ModPageSpeed 2.0: AVIF, WebP, and critical CSS — up to 69% less page weight on the live demo

Interaction to Next Paint (INP): diagnose it, then fix it

Interaction to Next Paint (INP) is the Core Web Vital that measures responsiveness: how long the user waits between a click, tap, or keypress and the next frame the browser paints in response. It replaced First Input Delay in March 2024. Unlike its predecessor it counts the whole interaction: not just the delay before your handler runs, but the handler itself and the paint that follows. That makes it the hardest vital to game, and the one most sites still fail.

Know this before you start: INP is the metric where a server-layer optimizer helps the least. The dominant cause is JavaScript running on the main thread, and that JavaScript lives in your application code, not at the server. This page gives you the diagnosis workflow and fixes split by where they actually live: the few things ModPageSpeed can do at the server layer, and the larger set of changes only your app can make. Then it hands you off to the per-platform guide that names the failure modes on your stack.

INP thresholds Good: ≤ 200 ms (p75, field) Poor: > 500 ms (p75, field)

What INP measures

INP is the worst-case latency between a user input (a click, tap, or keypress) and the next frame the browser paints in response. Chrome tracks every interaction over the page's lifetime and reports the 98th-percentile slowest one as that page's INP, so a single janky click late in a session can set the number. The good threshold is ≤ 200 ms at the 75th percentile of real users; "needs improvement" runs from 200 ms to 500 ms; over 500 ms is poor.

Each interaction has three phases. Input delay is the time before your event handler starts running, usually the main thread being busy with other JavaScript. Processing time is the handler itself doing its work. Presentation delay is the time to compute layout and paint the resulting frame. Diagnosis is about finding which phase dominates the worst interaction.

One thing matters more for INP than for any other vital: use the field number, not the lab number. Real-user INP comes from CrUX (and your Search Console Core Web Vitals report). The lab approximation from Lighthouse / PageSpeed Insights is unreliable because the lab issues no human input: there is no interaction to measure, so it estimates. Optimize against the field figure; it is the only one Google ranks on and the only one worth chasing. Analyze a page to see where it stands.

A single lab run from PageSpeed Insights approximates INP; the field number from real Chrome users is what Google ranks on.

Common causes

Long JavaScript tasks blocking the main thread

The dominant cause, and it lives in your application code. Event handlers, framework hydration, and startup work run as long tasks that occupy the main thread; an interaction that arrives mid-task waits behind it before the handler can start. This shows up as input delay in a DevTools trace.

Heavy event handlers

The handler itself doing too much. Classic offenders: jQuery validation re-running on every keystroke, hand-written synchronous DOM thrashing, or parsing a large JSON blob inside a click handler. This is processing time, and it is bounded below by whatever the slowest handler the user can trigger actually does.

Architectures that require a round-trip before paint

Some frameworks are INP-bound by design. Blazor Server sends every interaction over a SignalR connection, so INP is bounded below by network latency plus server render time: a transatlantic user can sit at the "needs improvement" boundary before any work is done. This is architectural, not an optimization problem.

Slow upstream behind interactive widgets

Autocomplete, infinite scroll, and "load more" fire a request on the user's input. If the /api/* endpoint has long-tail TTFB (un-tuned upstream, cold cache, an unindexed query) INP includes that wait whenever the handler is synchronous. The fix is in the upstream, not the page.

Third-party scripts you don't control

Intercom, HotJar, GTM-injected analytics, and chat widgets attach their own handlers and run in their own scheduler. They compete for the main thread and frequently sit on top of INP attribution. You can defer or remove them, but you can't rewrite them.

How to fix INP

Profile the worst interaction before changing anything

Application

INP is a worst-case metric: you cannot fix it without knowing which interaction records it. Record a real flow in the Chrome DevTools Performance panel and read the Interactions track for the slowest input, or log onINP from the web-vitals library in attribution mode to name the responsible script and handler. Replicate it under CPU and network throttling. The output you want is a specific interaction, a specific handler or endpoint, and whether the cost is input delay, processing, or paint.

Reduce startup JavaScript competing for the main thread

Server layer

This is the server layer's narrow but real lever. ModPageSpeed can rewrite_javascript (minify, safe by construction: no AST transforms or renaming, and it only writes a smaller variant), combine_javascript (concatenate files to cut per-file parser setup), and defer_javascript (defer scripts it can prove safe; the 2.0 worker uses browser analysis). That moves parser-blocking work off the critical path so it no longer collides with the first click. It helps when the origin serves heavy non-minified or legacy JS, and little when you already ship a lean, code-split SPA bundle. defer_javascript is marked Test first because it changes execution order: profile, then stage it.

Shrink and split the handlers themselves

Application

This is where most INP wins live, and it is application work. Keep handlers lightweight; break long tasks up and yield to the main thread (scheduler.yield()) so a long handler doesn't block the next paint; debounce per-keystroke work; move analytics and other non-critical callbacks out of the interaction's critical path. Replace jQuery validation that re-runs on every keystroke with native HTML5 validation. No server-layer filter can shrink a 90 ms handler; only rewriting it can.

Fix the upstream behind interactive widgets

Server + app

If INP attribution lands on a fetch('/api/...') that takes 300 ms to respond, INP is at least 300 ms and no rewriter changes that. Fix it at the source: caching, database indexing, async I/O. On nginx specifically, remove connection overhead too: set keepalive on upstream blocks (with proxy_http_version 1.1 and an empty Connection header), raise worker_connections so bursts don't queue, and enable HTTP/2 to end HTTP/1.1 head-of-line blocking. These cut per-interaction connection cost; they don't shorten handler latency.

Defer or remove third-party scripts

Application

When attribution sits on Intercom, HotJar, a chat widget, or GTM-injected analytics, the rewrite layer can't help: they run in their own scheduler from another origin. Move them behind a tag manager that fires after first paint or first idle, or remove the ones that do no user-visible work. This is configuration and product decisions, not a server filter.

Re-architect when INP is structural

Application

Some INP failures cannot be optimized away. Blazor Server's SignalR round-trip is a design floor: the fix is Blazor WebAssembly or @rendermode InteractiveAuto, which run in the browser. Third-party JS controls (Telerik, Syncfusion, DevExpress) are bounded by their own render path. Geographic distance from the origin adds round-trip latency to every interactive request; a CDN or co-located cache helps, but the page optimizer can't shorten the speed of light. We will not pretend a server filter fixes these.

Fix INP on your platform

The same metric fails for different reasons on different stacks. Pick yours for the concrete diagnosis and fix.

INP: common questions

What is a good INP score?
200 ms or less is good, measured at the 75th percentile of your real users in the field. From 200 ms to 500 ms is "needs improvement," and anything over 500 ms is poor. Because INP reports the 98th-percentile slowest interaction over the page's lifetime, one consistently slow click can fail an otherwise fast page.
How do I fix INP?
Profile first: record a real interaction flow in the Chrome DevTools Performance panel (or log onINP with the web-vitals attribution build) to find the single worst interaction and which phase dominates it. Then fix where the cost lives: shrink and split the responsible handler, move third-party scripts out of the critical path, and fix slow /api/* upstreams. At the server layer, ModPageSpeed can minify, combine, and defer startup JavaScript so it stops competing for the main thread, useful when the origin serves heavy legacy JS, less so when you already ship a lean SPA bundle.
What is the difference between INP and First Input Delay (FID)?
FID only measured the delay before your handler started running on the very first interaction. INP, which replaced it as a Core Web Vital in March 2024, measures the full interaction: input delay plus the handler's processing time plus the paint, across every interaction on the page, reporting the worst. That makes INP a truer measure of responsiveness, and harder to game.
Why is my lab INP different from my field INP?
The lab number from Lighthouse / PageSpeed Insights is unreliable because the lab issues no human input: with no interaction to measure, it estimates. Real INP comes from CrUX field data (and your Search Console Core Web Vitals report). Always optimize against the field figure; it is the only one Google ranks on.
Can ModPageSpeed fix my INP?
Indirectly, and only sometimes. INP measures JavaScript main-thread responsiveness, and a server-layer optimizer does not rewrite your application logic; it will not make a slow click handler fast. What it can do is reduce how much JavaScript competes for the main thread before the user interacts, by minifying (rewrite_javascript), combining (combine_javascript), and deferring scripts proven safe (defer_javascript). That lowers INP on pages throttled by startup script execution: a lot when the origin serves heavy non-minified JS, little when the stack already ships a lean, code-split bundle. The available filters run identically across mod_pagespeed 1.15 (nginx / Apache module) and the WeAmp.PageSpeed ASP.NET Core middleware.
Why can't a server-layer tool fix Blazor Server INP?
Blazor Server sends every interaction over a SignalR connection: a button click is network latency plus server render time plus applying the DOM diff. INP is bounded below by that round-trip by design, so no rewriter operating on the response stream can shorten it. The fix is architectural: Blazor WebAssembly or @rendermode InteractiveAuto, which move execution into the browser. See the ASP.NET Core INP guide for the full Razor-versus-Blazor split.

Measure, then fix

Start with a measurement. Run a page through the analyzer to see what is driving its INP, then fix it at the server layer with ModPageSpeed — it optimizes immediately, licensed or not. Production use requires a commercial license — but the software never locks you out.

See also: