Core Web Vitals: INP for the modern stack
INP replaced FID in 2024. How to diagnose and fix it on React, Vue and SSR stacks, with the exact DevTools workflow that gets fixes shipped in a sprint.
1. What changed when INP replaced FID
First Input Delay measured one thing: the delay before the browser began processing the first interaction. Interaction to Next Paint measures every interaction across the full page lifetime, then reports the 98th percentile. It's harder to game and closer to what users feel.
2. Diagnosing INP in 20 minutes
- 01Open the PageSpeed Insights report, INP is measured from field data (CrUX), so the number reflects real users.
- 02Open Chrome DevTools → Performance panel → "Interactions" track. Record while interacting with the top-3 user paths.
- 03Look for interactions with a red block on the Interactions track. That's your problem child.
- 04Zoom into the block; find the long task. It's almost always a synchronous handler, hydration burst, or unbatched state update.
3. React: the four INP killers
- Uncontrolled re-renders after input change, wrap child components in memo or lift state closer to where it's used.
- Heavy work in onChange / onClick handlers, defer to requestIdleCallback or wrap non-urgent updates in startTransition.
- Hydration mismatches that trigger full-tree re-render, server and client output must match exactly.
- Third-party scripts (chat widgets, tag managers) that hijack the main thread on first click.
// React 18 patternimport { startTransition, useState } from "react"; function Search() { const [input, setInput] = useState(""); const [results, setResults] = useState<Result[]>([]); const onChange = (v: string) => { setInput(v); // urgent, keeps the input responsive startTransition(() => { setResults(compute(v)); // deferred, INP-friendly }); }; return <input value={input} onChange={(e) => onChange(e.target.value)} />; }
4. SSR frameworks and the hydration cost
Next.js, Remix and TanStack Start ship JavaScript that must hydrate before the page becomes interactive. Two techniques reliably move the needle:
- Selective hydration, only hydrate what's above the fold on first paint. Islands architecture is the extreme version.
- Streaming server rendering, flush HTML as soon as it's ready, hydrate incrementally as chunks arrive.
5. The third-party tax
The most reliable single-day INP win is not code, it's audit and cull. Intercom, Segment, HubSpot, Hotjar, ad pixels: each one is a long-task machine. Load them on interaction rather than on document ready.
// delayed loader// Load chat widget only after the user proves they want it const idle = window.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 2000)); idle(() => { const s = document.createElement("script"); s.src = "https://widget.intercom.io/widget/APP_ID"; s.async = true; document.body.appendChild(s); });
6. A one-sprint fix plan
| Day | Task | Expected INP impact |
|---|---|---|
| Mon | Baseline CrUX + Performance recordings on top-3 paths | 0ms, you're measuring |
| Tue | Wrap non-urgent setState in startTransition | -30 to -80ms |
| Wed | Defer or cull 2 third-party scripts | -60 to -150ms |
| Thu | Selective hydration or component memoization pass | -40 to -100ms |
| Fri | Ship, re-measure with WebVitals JS in production | confirms field data |
- ✓INP measures the 98th percentile of every interaction, much harder to fake than FID.
- ✓In React, startTransition is the single highest-leverage change.
- ✓SSR frameworks pay a hydration tax; selective hydration is the answer.
- ✓The fastest single-day win is culling or deferring third-party scripts.