Heavy event handler: why a click takes 500 ms and how to fix it
What is happening
When a user clicks, the browser runs your event handler to completion before it can paint the next frame. If the handler does hundreds of milliseconds of synchronous work — filtering a large array, building DOM, parsing JSON — the page is frozen for exactly that long. The user sees nothing happen until the handler returns.
This is the most common INP problem, and it shows up in the INP breakdown as a long processing time phase.
How to recognise it
- Processing time dominates the INP breakdown (input delay and presentation delay are small).
- In DevTools Performance, one long task starts right at the event and is attributed to your handler function.
- The slowness scales with data size: the more rows/items, the worse the click feels.
How to fix it
- Update the UI first (pressed state, spinner), then yield to the browser with scheduler.yield() (or setTimeout(0) as a fallback) before doing the heavy work.
- Split big loops into chunks, yielding between chunks so the browser can paint.
- Move pure computation (parsing, filtering, diffing) to a Web Worker — the main thread only sends input and receives the result.
Problem
button.addEventListener("click", () => {
const results = filterRows(allRows) // 400 ms of sync work
renderTable(results) // user saw nothing until now
})
Fix
button.addEventListener("click", async () => {
button.classList.add("loading") // instant feedback, painted first
await scheduler.yield() // let the browser paint
const results = filterRows(allRows)
renderTable(results)
button.classList.remove("loading")
})
Scan your site for free →