Performance and DevTools
Investigate a slow interaction
Find the input delay, handler work, and rendering delay behind an interaction that feels unresponsive.
When a click feels slow, we blame the click handler. Often the handler is fine. The time went somewhere else.
An interaction has three parts, and INP measures all of them together:
- Input delay. The event is queued but the main thread is busy with something else, so the handler can’t start.
- Processing time. Your event handlers run, plus everything they call synchronously.
- Presentation delay. The browser does style, layout, paint, and compositing to show the result.
Knowing which part is slow tells you what to fix. Rewriting a handler that takes 5 milliseconds does nothing when it waited 300 milliseconds behind an analytics script. Splitting JavaScript into smaller chunks does nothing when the handler triggers a layout of 2,000 rows.
Read the recording
Record the slow interaction in the Performance panel. Click the interaction marker and DevTools highlights the three phases on the Main track.
- Work before your handler starts is input delay. Expand it to see which task was running.
- Wide function calls inside the handler are processing time. The widest one is your target.
- Rendering work after the handler is presentation delay. Look for a big Layout or Paint block.
Fix the dominant part
For input delay, break up the long task that was running. Yield to the browser with setTimeout() or scheduler.yield() so input can get in.
For processing time, do less. Remove work whose result nobody sees, and move follow-up work out of the handler. Update the DOM first so the visitor sees a result, then do the rest in a later task.
For presentation delay, touch less of the DOM. Update the one element that changed instead of re-rendering the list. Avoid reading layout right after writing it, which forces a synchronous layout.
Add a fake long task
Take any button on a page and add a fake long task to its handler:
button.addEventListener('click', () => {
const end = performance.now() + 300
while (performance.now() < end) {}
badge.textContent = 'Added'
})
Record the click. The badge updates after 300 milliseconds, and the interaction marker shows it all as processing time.
Now move the DOM update first and push the loop into a setTimeout(). Record again. The badge appears in the next frame, and the loop runs afterwards without hurting INP.
One more thing. INP takes one of the slowest interactions of the whole visit, so a fast average hides a painful outlier. Test your important interactions one by one, and look at the slow end of your field data, not the middle.
Lesson completed