On 12 March 2024, Google replaced First Input Delay with Interaction to Next Paint as a Core Web Vital. Plenty of sites that had comfortably passed for years suddenly showed red in Search Console without changing a line of code. The site did not get slower. The test got stricter, and much more honest.
What changed between FID and INP
FID only measured the delay before the browser started handling the very first interaction on a page. It ignored how long the handling took and ignored every interaction after the first. Almost every site passed it.
INP looks at every click, tap and key press during the whole visit and reports roughly the slowest one. It also measures the full journey: from the moment you tap until the next frame visibly reflects the result.
- Good: 200 milliseconds or less at the 75th percentile.
- Needs improvement: 200 to 500 milliseconds.
- Poor: over 500 milliseconds.
The three phases of an interaction
- Input delay: the browser is busy with something else, such as a long script, and cannot start handling your tap.
- Processing duration: your event handlers run. Heavy handlers make this long.
- Presentation delay: the browser recalculates styles and layout and paints the new frame. Huge DOM updates make this long.
Each phase has a different fix, so the first job is finding out which one dominates your slow interactions.
Find the slow interaction
Lab tools like Lighthouse cannot measure INP because nobody is clicking. Start with field data, then reproduce locally.
- Check the Core Web Vitals report in Search Console to see which page groups fail INP.
- Open the page in Chrome, open the DevTools Performance panel and watch the live INP readout as you click around. It names the slowest interaction.
- Record a trace while repeating that interaction. Long tasks show as blocks with a red corner; click one to see which script ran.
- For real-user detail, log INP attribution from your own visitors with the web-vitals library.
import { onINP } from "web-vitals/attribution"
onINP(({ value, attribution }) => {
navigator.sendBeacon("/api/vitals", JSON.stringify({
inp: value,
target: attribution.interactionTarget,
inputDelay: attribution.inputDelay,
processing: attribution.processingDuration,
presentation: attribution.presentationDelay,
}))
})Fix long input delay
Input delay means the main thread was busy when the visitor tapped. The usual culprits are third-party scripts, analytics, chat widgets and A/B testing tools, running large tasks during and after load. Load them after the page is interactive, or only when they are needed.
Fix long processing
Do the minimum needed to update the screen, then hand control back to the browser before doing the rest. Yielding lets the browser paint the visible response first, which is exactly what INP measures.
function yieldToMain() {
if (globalThis.scheduler?.yield) return scheduler.yield()
return new Promise((resolve) => setTimeout(resolve, 0))
}
button.addEventListener("click", async () => {
showSpinner() // visible feedback first
await yieldToMain() // let the browser paint it
recalculateCart() // heavy work afterwards
sendAnalytics()
})Fix long presentation delay
- Keep the DOM small. Pages with many thousands of elements take longer to restyle on every change.
- Avoid reading layout values such as offsetHeight straight after changing styles, which forces extra layout work.
- Use content-visibility: auto on long sections that are off screen so the browser can skip rendering them.
- Render long lists virtually instead of all at once.
Quick win
LightLoader delays non-critical third-party scripts until the page is interactive, which removes the most common source of input delay without touching your code. For your own scripts, yielding and smaller DOM updates do the rest.