
You click “Add to cart” on a page that finished loading a while ago. Nothing happens for half a second. Then the spinner, then the cart count, then the drawer. Nothing in that sequence is a loading problem — the HTML, CSS, and images arrived long before the click. The delay is the browser waiting for your own JavaScript to finish whatever it was doing.
That gap between input and visible response is what Interaction to Next Paint measures, and it is the Core Web Vital most WordPress sites fail without noticing. LCP failures are visible in a waterfall. INP in WordPress failures are visible only when someone uses the page. If load time is still the problem, our guide to hitting 90+ on Core Web Vitals covers that half; this is the other one.
What INP Measures, Precisely
INP observes the latency of click, tap, and keyboard interactions across the whole visit. Hovering, zooming, and scrolling are not counted. An interaction can involve several events — a tap fires pointerdown, pointerup, and click — and the metric treats them as one group.
Each interaction has three parts:
- Input delay: time before the first event handler runs, usually because the main thread is busy with something else.
- Processing time: how long your handlers take to execute.
- Presentation delay: time from the end of the handlers until the browser paints the next frame.
The final value is the longest interaction observed, ignoring outliers. The exact rule from web.dev’s INP documentation is that one highest interaction is ignored for every 50 interactions, which keeps a single random hiccup from defining a fast page. INP is calculated when the visitor leaves the page.
The threshold is measured at the 75th percentile of page views, separately for mobile and desktop:
- 200 ms or less: good
- 200–500 ms: needs improvement
- Above 500 ms: poor
INP replaced First Input Delay as the responsiveness metric. FID only measured the input delay of the first interaction; a page could pass FID and still freeze on the second click. That is why a site can have a healthy FID history and a failing INP report — they were never measuring the same thing.
Why WordPress Sites Fail INP More Than LCP
A WordPress site accumulates JavaScript the way a kitchen accumulates gadgets. A slider plugin, a page builder runtime, a cookie banner, a chat widget, two analytics tags, a heatmap, a font loader, and the theme’s own main.js. Each one ships a file, and most of them attach at least one event listener to document or window.
The consequence is that a single click can wake up handlers from five plugins. Most will check whether the click happened inside their own element and return immediately. One will do real work — fetch a fragment, recalculate a layout, re-render a carousel — and the browser cannot paint until the whole queue clears.
There is a second WordPress-specific pattern: work triggered by the interaction is often a network request to admin-ajax.php, followed by DOM updates when it returns. The request itself is not the problem if the browser paints a pending state first. It is a problem when the handler blocks the main thread before that paint, which is exactly what “nothing happened for 600 ms” feels like.
None of this shows up in an HTML cache. A page can be served from cache in 80 ms and still take 400 ms to respond to a click.
Find the Interaction That Is Actually Slow
Start with field data, because your laptop is not the phone your visitors use. The Core Web Vitals report in Search Console groups URLs by status and shows whether mobile, desktop, or both are failing. PageSpeed Insights shows the same field data alongside a lab run. The field report tells you which template to investigate; the lab run lets you reproduce it with CPU throttling.
Then measure the interaction itself. The Event Timing API is what INP is built on, and you can watch it in the console on any page:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.interactionId) continue;
console.log(
entry.name,
Math.round(entry.duration) + 'ms',
entry.target
);
}
}).observe({ type: 'event', buffered: true, durationThreshold: 40 });
Three details make this usable:
interactionIdis present only for real interactions, which filters out the noise.durationThresholddefaults to 104 ms. Setting it to 40 shows near misses, which is what you want while diagnosing.- The log is per interaction, so the slowest line is your INP candidate, not an average.
Reproduce the slow one under throttling. In Chrome DevTools, open the Performance panel, set CPU to 4× or 6× slowdown, start recording, and click the control. The flame chart will show whether the delay is a long task before your handler runs, a long handler, or a rendering stall after it. Those are three different fixes.
If you want the lab-only view without recording, Total Blocking Time is the closest proxy: it sums the main-thread blocking during load. It correlates with INP, but it cannot see the click handling that happens minutes later, so treat a good TBT as permission to look further, not as proof.
The Usual Suspects, in the Order Worth Checking
1. Third-party tags. Chat widgets, heatmaps, A/B testing tools, and tag managers are the most common cause of input delay because they run their own timers and listeners on your main thread. Test by blocking their domains in DevTools → Network → right-click → Block request domain, then repeat the interaction. If the delay disappears, you have found the cause and can decide whether the tool earns its cost.
2. Page builders and sliders. These ship a runtime that initializes every widget on the page and often recalculates layout on resize or scroll. A slider with autoplay is worse: it schedules work on a timer, and that work can land in the middle of a click. Check the DOM node count in the DevTools console with document.querySelectorAll('*').length. A page builder product page above roughly 1,500 nodes is worth auditing.
3. WooCommerce cart fragments. The wc-cart-fragments script refreshes the mini-cart through admin-ajax.php on every page load, including pages that have no cart. It is a load-time cost that competes with early interactions, and it is covered separately below.
4. jQuery event delegation. Older plugins bind a single handler to document and let clicks bubble up. That handler runs for every click on the page, even when the target is unrelated. The handler is usually cheap; the plugin’s follow-up work is not.
5. Click handlers that wait on the network before painting. A handler that calls fetch() and only then updates the button state leaves the user staring at an unchanged screen. Paint the pending state first, then await the request.
Remove, Defer, Split
The fixes rank in that order, and most sites reach for the third before trying the first.
Remove. Audit plugins by what they add to the front end, not by what they claim. A slider that is used on two pages should not enqueue its runtime site-wide. A chat widget that nobody staffs is pure cost. Deactivate one at a time and measure the same interaction.
Defer. Since WordPress 6.3, wp_enqueue_script() accepts a strategy argument, documented on the function reference:
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'awp-ui',
get_template_directory_uri() . '/assets/ui.js',
array(),
'1.0',
array(
'strategy' => 'defer',
'in_footer' => true,
)
);
}, 20 );
defer downloads in parallel and executes after parsing, in order. async executes as soon as it arrives, which is rarely what a UI script wants. For a plugin handle you do not control, the older filter still works:
add_filter( 'script_loader_tag', function ( $tag, $handle ) {
if ( 'plugin-handle' === $handle && ! is_admin() ) {
return str_replace( ' src=', ' defer src=', $tag );
}
return $tag;
}, 10, 2 );
Deferring changes execution order. A plugin that prints inline code expecting its own script to have run already will break, and the failure often appears as a console error rather than a visible bug. Test the interaction you are optimizing and one form submission before shipping.
Split. A handler that does 300 ms of work does not have to do it in one task. Paint the visible change, yield, then finish the rest:
button.addEventListener('click', async () => {
button.disabled = true;
button.textContent = 'Adding…';
await new Promise((resolve) => setTimeout(resolve, 0));
await addToCart();
button.textContent = 'Added';
});
scheduler.yield() is a better primitive where it is supported, because it returns to the browser’s queue rather than the task queue. The setTimeout version works everywhere and is good enough for most WordPress themes. For genuinely expensive work — parsing a large JSON payload, filtering a long list — move it to a Web Worker. The guide to optimizing long tasks covers the yielding patterns in more detail.
The WooCommerce Cart Fragment Case
On a default WooCommerce install, wc-cart-fragments loads on every page. Its job is to keep the mini-cart count accurate when a visitor adds something in another tab or the cart session changes. On a blog post with no cart in the header, that is a request and a script execution for nothing.
This removes it outside WooCommerce pages:
add_action( 'wp_enqueue_scripts', function () {
if ( ! function_exists( 'is_woocommerce' ) ) {
return;
}
if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
wp_dequeue_script( 'wc-cart-fragments' );
}
}, 20 );
The caveat is in your header. If the theme shows a mini-cart on every page, dequeueing the script leaves that count stale after a session change. Most stores can live with that on blog posts; a store with a persistent header cart should test it. Verify in DevTools → Network, filter for admin-ajax.php, and reload a blog post. The woocommerce_get_refreshed_fragments request should be gone.
This is a load-time win more than an INP win, and that is the honest framing. It frees the main thread earlier, which helps the first interaction on a page, but it will not fix a 400 ms click handler.
What Does Not Fix INP in WordPress
More caching. A page cache, object cache, or CDN reduces time to first byte and helps LCP. None of them change how long a click handler runs in the browser. If you are choosing between caching work and JavaScript work, our comparison of cache plugins for WordPress is the right starting point for the first, and this article is about the second.
Image compression. Same category. Smaller images improve LCP and bandwidth. They do not shorten a task on the main thread. Image optimization is still worth doing; it is just not an INP fix.
A 100 in Lighthouse. Lighthouse runs a synthetic load and reports Total Blocking Time. It does not measure INP, because there is no real user to interact with the page. A green lab score with a red field report is a normal outcome, not a contradiction.
A CDN with “JavaScript optimization” enabled. Some CDNs offer to defer or delay scripts automatically. That can help, and it can also break a plugin’s initialization order. Measure the interaction before and after; do not trust the toggle. If you are evaluating edge providers, our list of CDN services covers what each one actually caches.
Removing jQuery entirely. jQuery is not the problem. A plugin that runs a heavy handler on every click is the problem, and it will still run if you replace the library.
Verifying a Fix Without Fooling Yourself
Field data moves slowly. Search Console’s Core Web Vitals report covers the last 28 days of real-user measurements, so a fix shipped today will not appear tomorrow, and a single good day will not flip a Poor group. Give a change at least a couple of weeks before judging it.
In the meantime, verify the mechanism, not the score:
- Record the same interaction on the same page with the same CPU throttle, before and after.
- Check the console observer output. The slowest interaction should drop, and the near-miss list should get shorter.
- Test one unrelated interaction — a menu toggle, a form submit — to catch a defer that broke initialization.
- Check the browser console for new errors on the page where you changed script order.
Keep a short log: date, URL, interaction, before, after. When the field data finally moves, that log is what tells you which change did it. If you want a broader pass over the site after a round of changes, our functionality, speed, and security checklist covers the checks that catch regressions outside the metric.
One more comparison worth making: a site that renders HTML on the server has less JavaScript to execute before the first interaction. That is the argument behind headless WordPress with Astro, and it is also the argument for keeping a traditional theme lean. INP rewards less JavaScript, not better JavaScript.
FAQ
Is INP a Core Web Vital?
Yes. It replaced First Input Delay as the responsiveness metric and is reported alongside LCP and CLS in Search Console and PageSpeed Insights. The good threshold is 200 ms at the 75th percentile.
Why is my INP bad on mobile but fine on desktop?
Mobile devices have slower CPUs and less memory, so the same handler takes longer to execute and the browser takes longer to paint afterward. Test with CPU throttling rather than assuming the network is the cause. A fast connection does not make a slow processor faster.
Does a caching plugin improve INP?
Not directly. Caching reduces server response time and can free the main thread earlier during load, but it does not shorten a click handler. If the delay happens after the page has loaded, the fix is in JavaScript, not in the cache layer.
Should I defer all JavaScript in WordPress?
No. Deferring scripts that another script depends on, or that must run before first paint, causes initialization errors. Defer the ones that are not needed for the first screen, test the interactions they power, and leave the rest alone.
Why does PageSpeed Insights show a good score when Search Console shows poor INP?
They measure different things. PageSpeed Insights runs a lab test and reports proxies such as Total Blocking Time; Search Console reports field INP from real visitors. The field report is the one Google uses, so trust it over the lab score.
Can I fix INP without touching code?
Sometimes. Removing an unused plugin, disabling a chat widget, or turning off a slider’s autoplay can remove the handler entirely. Once the fix requires deferring or splitting scripts, you need code access or a developer.
Does INP affect rankings?
Core Web Vitals are part of Google’s page experience signals, and INP is one of the three. It is not a substitute for relevant content, and a fast page with thin content still loses. Treat a passing INP as removing a disadvantage.