Measure real interactions
Track event latency across real user journeys and not just lab metrics.
Learn how to measure INP, avoid delayed event handling, and keep interactive experiences snappy in modern React apps.
Track event latency across real user journeys and not just lab metrics.
Keep event handlers lean and avoid heavy JavaScript blocking during interactions.
Faster interactive experiences mean fewer cart abandonments, fewer form drop-offs, and better growth outcomes.
Interaction to Next Paint measures the longest interaction delay observed across a user's entire session — not just the first click. Google replaced FID with INP as a Core Web Vitals ranking signal in 2024 precisely because FID only captured the first interaction and missed slow UI responses that happen mid-session during checkout or form completion.
React apps are particularly susceptible to high INP because state updates trigger synchronous re-renders of potentially large component trees. When a user clicks a button, React has to run reconciliation before the browser can paint the response — and every millisecond that takes adds to your INP score. The threshold for "Good" is 200ms. Above 500ms is "Poor" — territory where users notice lag and abandon interactions.
startTransitionReact 18's startTransition lets you mark a state update as low-priority. The browser will still process the user's immediate interaction (painting the pressed button state), then handle the expensive re-render asynchronously — without blocking the main thread:
// ❌ Blocks main thread on every keystroke
const handleSearch = (q: string) => {
setResults(heavyFilter(allItems, q));
};
// ✅ Search result update is deferred
const handleSearch = (q: string) => {
startTransition(() => {
setResults(heavyFilter(allItems, q));
});
};
This is especially effective on search inputs, filter panels, and any UI that computes derived state from a large dataset. Add a isPending indicator fromuseTransition so users see the UI respond immediately even while the result is computing.
JavaScript is single-threaded. Any synchronous computation in an event handler blocks the main thread and prevents the browser from painting. For genuinely CPU-bound work — sorting large arrays, parsing CSV data, running complex filters — move the computation to a Web Worker:
// worker.ts
self.onmessage = ({ data }) => {
const result = heavyComputation(data);
self.postMessage(result);
};
// component.tsx
const worker = new Worker(new URL("./worker.ts", import.meta.url));
worker.postMessage(inputData);
worker.onmessage = ({ data }) => setResult(data);
Next.js 15 supports Web Workers out of the box with no additional webpack configuration. The worker runs in a separate thread, so your main thread stays free to respond to user input and paint immediately.
Reading layout properties like element.offsetHeight,getBoundingClientRect(), orscrollTop inside a click handler forces the browser to recalculate layout synchronously — adding significant time to your INP. This is called "forced reflow" and is one of the most common INP killers in production React apps.
The fix is to read layout values before the event fires (store them in a ref during a resize observer or scroll event) rather than reading them inside the click handler. Chrome DevTools' Performance panel will show these as long purple "Recalculate Style / Layout" blocks in your interaction trace.
If a click triggers a state change that causes a large subtree to re-render — a data table, a sidebar, a complex product grid — React will block the main thread for the entire reconciliation. Wrapping those subtrees in Suspense with a lightweight skeleton allows React to commit the "loading" state first and defer the heavy render to a later frame, which dramatically reduces the blocking time the browser attributes to that interaction.
Lighthouse doesn't measure INP — it only reports Total Blocking Time, which is a proxy. Real INP scores come from field data: actual users interacting with your app on their devices and networks. The two ways to capture this are the web-vitals JS library (self-hosted) or a monitoring tool that captures Real User Monitoring (RUM) data automatically.
AuditJet's Real User Monitoring captures 75th-percentile INP from actual visitors with a sub-1KB script tag — no cookies, GDPR-friendly. When INP regresses, the platform alerts your team and generates a fix blueprint pointing at the specific interaction and component causing the delay.
Use AuditJet to turn React performance intelligence into faster, more reliable experiences for users and customers.