Subchapter 3.2
references/INP.mdMarkdown4 KBView on GitHub
Read this reference when field INP is poor, a trace identifies a slow interaction, or source inspection finds interaction work that needs runtime verification.
INP spans three phases. Do not optimize the event handler until the trace shows which phase dominates.
| Phase | Evidence to inspect | Typical fixes |
|---|---|---|
| Input delay | Long tasks already occupying the main thread before the event callback starts | Reduce startup work, split long tasks, delay third parties |
| Processing time | Event callbacks and synchronous work attached to the interaction | Remove unnecessary work, simplify handlers, use workers for CPU-heavy computation |
| Presentation delay | Style, layout, paint, or later main-thread work before the next frame | Reduce DOM scope, rendering cost, and layout invalidation |
Bad:
function processLargeArray(items) {
items.forEach(item => expensiveOperation(item));
}Good:
async function processLargeArray(items) {
const chunkSize = 100;
for (let i = 0; i < items.length; i += chunkSize) {
items.slice(i, i + chunkSize).forEach(expensiveOperation);
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
} else {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
}Choose chunk boundaries from trace evidence. A fixed item count does not guarantee an acceptable task duration on representative devices.
Bad:
button.addEventListener('click', () => {
const result = calculateComplexThing();
updateUI(result);
trackEvent('click');
});Good:
button.addEventListener('click', async () => {
button.classList.add('loading');
if ('scheduler' in window && 'yield' in scheduler) {
await scheduler.yield();
}
const result = calculateComplexThing();
updateUI(result);
if ('requestIdleCallback' in window) {
requestIdleCallback(() => trackEvent('click'));
} else {
setTimeout(() => trackEvent('click'), 0);
}
});Yielding helps only when the UI update can paint before the remaining work. Confirm the frame in the trace.
This observer reports interactions seen in the current page session. It is not field INP.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 200) {
console.warn('Slow interaction', {
type: entry.name,
duration: entry.duration,
processingStart: entry.processingStart,
processingEnd: entry.processingEnd,
target: entry.target
});
}
}
}).observe({ type: 'event', buffered: true, durationThreshold: 40 });For production attribution, prefer the web-vitals/attribution build. Its onINP() attribution can identify the interaction target, event type, and Long Animation Frame or script evidence available for real visits.