debugjavascriptMajor
INP: slow interaction to next paint caused by long tasks on the main thread
Viewed 0 times
INPinteraction to next paintlong tasksscheduler.yieldmain threadresponsiveness
Problem
Interaction to Next Paint (INP) is above 200ms. Clicks or keypresses feel sluggish because JavaScript on the main thread is not yielding, blocking the browser from painting the response frame.
Solution
Break long tasks into smaller chunks using scheduler.yield() or setTimeout(0) so the browser can paint between work items. Move non-UI work to a Web Worker.
// Break a long loop with scheduler.yield()
async function processItems(items) {
for (let i = 0; i < items.length; i++) {
processOne(items[i]);
// yield every 50 items to unblock the main thread
if (i % 50 === 0) {
await scheduler.yield();
}
}
}
// Fallback for browsers without scheduler.yield
function yieldToMain() {
return new Promise(resolve => setTimeout(resolve, 0));
}
// Break a long loop with scheduler.yield()
async function processItems(items) {
for (let i = 0; i < items.length; i++) {
processOne(items[i]);
// yield every 50 items to unblock the main thread
if (i % 50 === 0) {
await scheduler.yield();
}
}
}
// Fallback for browsers without scheduler.yield
function yieldToMain() {
return new Promise(resolve => setTimeout(resolve, 0));
}
Why
The browser can only paint after the current task finishes. A 500ms uninterrupted JS task means the user sees no visual feedback for 500ms after clicking. scheduler.yield() queues a microtask that lets the browser slip in a paint before continuing.
Gotchas
- scheduler.yield() is a relatively new API — check caniuse.com and provide a setTimeout(0) fallback
- Long tasks in Lighthouse are tasks over 50ms; INP measures the worst interaction, not the average
- Third-party scripts are a major INP culprit — use PerformanceObserver with longtask type to attribute them
- React renders are synchronous by default; React 18 concurrent features can help defer non-urgent updates
Code Snippets
Capture INP candidates in the browser
// Measure INP with PerformanceObserver
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.interactionId) {
console.log('INP candidate:', entry.duration, entry);
}
}
}).observe({ type: 'event', buffered: true, durationThreshold: 16 });Context
When users report sluggish interactions or INP is flagged in field data (CrUX) or Lighthouse
Revisions (0)
No revisions yet.