INP (Interaction to Next Paint) replaced FID as a Core Web Vital in March 2024. It measures the time from a user interaction (click, keypress, tap) to when the browser paints the next frame in response. Poor INP is most commonly caused by long JavaScript tasks blocking the main thread during WordPress page interactions.
Problem: A WordPress or WooCommerce page scores poorly on INP (Interaction to Next Paint) — clicks on filters, tabs, or cart buttons feel sluggish even though the page loads quickly, and the cause is not obvious from Lighthouse alone.
Solution: Profile INP with Chrome DevTools Performance panel — look for long tasks (>50ms) in the main thread after a user interaction. The most common WordPress INP offenders are synchronous WooCommerce cart JavaScript, undeferred third-party scripts, and large layout recalculations triggered by DOM manipulation. Fix with defer/async script attributes, debouncing, and breaking long tasks with scheduler.yield().
The examples below measure INP with the web-vitals library, identify long tasks with PerformanceObserver, and show the most effective patterns for breaking up main-thread work in WordPress JavaScript.
// Measure INP using the web-vitals library (from Google)
// npm install web-vitals or use the CDN version
import { onINP } from 'web-vitals';
onINP( ( metric ) => {
// metric.value in milliseconds
// Good: < 200ms Needs improvement: 200–500ms Poor: > 500ms
console.log( `INP: ${metric.value}ms (rating: ${metric.rating})` );
// Send to your analytics endpoint
if ( typeof wp !== 'undefined' && wp.apiFetch ) {
wp.apiFetch( {
path: '/myplugin/v1/vitals',
method: 'POST',
data: {
metric: 'INP',
value: metric.value,
url: location.href,
attribution: metric.attribution,
},
} );
}
} );
// Observe long tasks — anything over 50ms blocks INP
const observer = new PerformanceObserver( ( list ) => {
for ( const entry of list.getEntries() ) {
if ( entry.duration > 50 ) {
console.warn( `Long task: ${entry.duration.toFixed(0)}ms`, entry );
}
}
} );
observer.observe( { type: 'longtask', buffered: true } );
Break up long tasks to improve INP:
// Pattern 1: yield to the main thread between chunks using scheduler.yield()
// (Chrome 115+ / Safari 18+) or setTimeout fallback
async function processLargeList( items ) {
const CHUNK_SIZE = 50;
const results = [];
for ( let i = 0; i < items.length; i += CHUNK_SIZE ) {
const chunk = items.slice( i, i + CHUNK_SIZE );
results.push( ...chunk.map( processItem ) );
// Yield — lets the browser handle clicks/input between chunks
if ( 'scheduler' in globalThis && 'yield' in scheduler ) {
await scheduler.yield();
} else {
await new Promise( resolve => setTimeout( resolve, 0 ) );
}
}
return results;
}
// Pattern 2: Move heavy computation off the main thread with a Web Worker
// worker.js
self.addEventListener( 'message', ( e ) => {
const result = heavyComputation( e.data );
self.postMessage( result );
} );
// main.js
const worker = new Worker( '/wp-content/plugins/myplugin/worker.js' );
button.addEventListener( 'click', () => {
worker.postMessage( largeDataset );
} );
worker.addEventListener( 'message', ( e ) => {
updateUI( e.data ); // back on main thread — fast DOM update only
} );
NOTE: The most common INP offenders on WordPress sites are: WooCommerce cart drawer JavaScript, third-party chat widgets, and heavy click handlers that run synchronous DOM queries. Profile interactions in Chrome DevTools → Performance panel by clicking "Record" and then performing the slow interaction.