Google’s Core Web Vitals are no longer optional for SEO. In 2026, these metrics directly influence search rankings, and INP (Interaction to Next Paint) has become the most challenging one for WordPress sites. While LCP and CLS can be fixed with image optimization and layout reservations, INP requires fundamentally rethinking how JavaScript runs on your pages.
INP replaced FID (First Input Delay) as a Core Web Vital in March 2024. The difference is significant: FID only measured the first interaction, while INP measures every interaction throughout the entire page visit. A site could score well on FID but terribly on INP - and many WordPress sites do.
Core Web Vitals thresholds for LCP, INP and CLS
Good and poor values for LCP, INP and CLS
| Metric | What It Measures | Good | Needs Work | Poor |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | Loading speed | < 2.5s | 2.5-4s | > 4s |
| INP (Interaction to Next Paint) | Responsiveness | < 200ms | 200-500ms | > 500ms |
| CLS (Cumulative Layout Shift) | Visual stability | < 0.1 | 0.1-0.25 | > 0.25 |
Why is INP harder to fix than LCP and CLS
LCP is primarily about server speed and image optimization - well-understood problems with clear solutions. CLS is about reserving space for dynamic content - a CSS and HTML discipline. INP is about JavaScript execution efficiency - a fundamentally harder problem because it requires understanding the browser’s main thread, task scheduling, and event handling.
WordPress sites are particularly vulnerable to poor INP because:
- Plugin bloat - every plugin can add JavaScript that blocks the main thread
- Page builders - Elementor, Divi, WPBakery add heavy frontend frameworks
- Third-party scripts - analytics, ads, chat widgets, social embeds all compete for main thread time
- jQuery dependency - many WordPress themes and plugins still rely on jQuery, adding 85KB+ of blocking JavaScript
- No code splitting - traditional WordPress themes load all JavaScript on every page
How INP works
What are the three phases of an interaction
When a user clicks a button, taps a link, or presses a key, the browser processes it in three phases:
- Input delay - time between the physical interaction and the browser starting to process the event handler (caused by main thread being busy with other tasks)
- Processing time - time to execute all event handlers for this interaction
- Presentation delay - time to render the visual update after event handlers complete
INP = Input delay + Processing time + Presentation delay
The metric reports the slowest interaction of the visit. On busy pages the rule is slightly softer: according to web.dev, one highest interaction is ignored for every 50 interactions. Most pages see fewer than 50 interactions per visit, so in practice the worst one is what gets reported. Google then rates the page at the 75th percentile of all visits recorded in the field. This means a single slow interaction can tank your INP score, even if every other interaction is fast. On a typical WordPress site with forms, menus, filters and carousels there are dozens of possible interactions, and one bad one is enough.
INP became a Core Web Vital and replaced FID on 12 March 2024, as announced on web.dev. FID was deprecated in the same transition and removed from the Search Console report.
What counts as an interaction
Not every user action registers as an interaction for INP purposes. The distinction matters because optimizing for the wrong events wastes effort.
These actions DO trigger INP:
- Clicks on buttons, links, toggles, menus, checkboxes, radio buttons
- Taps on mobile (touch events including
pointerdown,pointerup,click) - Key presses in input fields, search boxes, text areas, and contenteditable elements
- Keyboard shortcuts - pressing Enter to submit a form, Space to toggle a checkbox
- Custom interactive elements - any element with a
click,pointerdown, orkeydownhandler attached
These actions do NOT trigger INP:
- Scrolling - scroll is measured separately and does not count as a discrete interaction
- Hovering - mouse hover alone doesn’t generate an INP entry, even if a tooltip appears
- Pinch-to-zoom - this is a continuous gesture, not a discrete interaction
- CSS-only transitions - animations triggered purely by CSS (
:hover,:focus) without JavaScript event handlers - Page load - the initial rendering of the page is not an interaction; that’s what LCP measures
One nuance: a single physical action like a tap can generate multiple events (pointerdown, then mousedown, pointerup, mouseup and finally click). The browser groups these into a single interaction and measures the total duration from the first event to the final paint. This grouping means you don’t get penalized five times for one tap, but it also means slow handlers on any of those grouped events will increase the overall interaction duration.
Measuring INP on WordPress sites
Google Search Console (field data)
The most important data source. Navigate to Core Web Vitals, then Mobile or Desktop. Search Console shows real-user INP data aggregated over 28 days. Pages are grouped by URL pattern and rated as Good, Needs Improvement, or Poor.
Always start here. Field data reflects what real users experience, including slow devices, weak connections and interactions that lab tests never simulate. If Search Console reports poor INP, Google is seeing that problem, and it can affect rankings.
PageSpeed Insights (lab + field)
Enter any URL and get both:
- Field data - real user measurements from the Chrome UX Report (CrUX). This is the data Google uses for its assessment
- Lab data - simulated measurements from Lighthouse, run in a controlled environment
For INP, field data matters more because lab tests may not trigger the same interactions real users perform. A site can show a clean lab report and still have poor field INP, because real users open complex menus, search filters and forms that Lighthouse never touches.
Chrome DevTools (debugging)
To diagnose a specific INP problem, Chrome DevTools is the tool. The process:
- Open DevTools, go to the Performance panel and click Record
- Interact with the page: click buttons, open menus, fill in forms
- Stop the recording and analyse the result
Look for:
- Long tasks - tasks over 50ms, marked with a red triangle in the corner. Any task above this threshold blocks the main thread
- Event handlers - how long each click or tap takes to process. If one click triggers several handlers, the time adds up
- Layout thrashing - forced synchronous layouts during interactions. This happens when JavaScript alternately reads and writes layout properties, forcing the browser to recalculate again and again
How to read a waterfall chart in DevTools
The Performance panel waterfall can be overwhelming, but a systematic approach makes it manageable. After recording an interaction, focus on these areas:
- Find the interaction marker - Chrome marks discrete interactions with a dashed vertical line in the timeline. Click it to highlight the associated events.
- Check the main thread flame chart - look directly below the interaction marker for the stack of function calls. The widest bars represent the longest-running functions - these are your optimization targets.
- Identify the three INP phases - input delay appears as idle time before the first event handler fires, processing time spans the event handler execution, and presentation delay covers style recalculation, layout, and paint after handlers complete.
- Look for forced reflows - purple “Layout” bars inside event handlers indicate synchronous layout calculations. These appear when JavaScript reads a layout property (like
offsetHeight) after modifying the DOM, forcing the browser to recalculate layout immediately rather than batching it. - Check the “Interactions” track - Chrome DevTools has a dedicated Interactions lane that shows each interaction’s total duration and its rating (green/yellow/red). This gives you a quick pass/fail before diving into the flame chart.
A common pattern on WordPress sites: clicking a menu toggle triggers a 200ms long task because the event handler forces a reflow by reading element.getBoundingClientRect() immediately after adding a CSS class. Moving the DOM read before the DOM write eliminates the forced reflow and can cut interaction time by 60-80%.
How to improve INP in WordPress
1. Defer non-critical JavaScript
The single most impactful fix. Most WordPress plugins load JavaScript in the <head>, blocking the main thread before the page even renders:
<!-- Before: blocking -->
<script src="plugin-slider.js"></script>
<!-- After: deferred -->
<script src="plugin-slider.js" defer></script>The defer attribute tells the browser to download the file in parallel but execute it only after the HTML has been fully parsed. That frees the main thread during the initial load and cuts input delay for early interactions.
Plugins that benefit most from deferring:
- Analytics (GA4, GTM) - add
deferor load viarequestIdleCallback - Chat widgets (Intercom, Tawk.to) - load after user scroll or after 5 seconds
- Social embeds (Facebook, Twitter) - load only when visible (Intersection Observer)
- Sliders and carousels - defer initialization until visible
2. Remove unused plugin JavaScript
Many plugins load their scripts on every page of the site, whether they are needed there or not. Audit which plugins actually need frontend JavaScript:
# List the scripts enqueued on the front end
wp eval 'do_action("wp_enqueue_scripts"); global $wp_scripts; foreach ($wp_scripts->queue as $s) echo $s . "\n";'wp eval runs outside a page request, so the wp_enqueue_scripts hook has to be fired by hand, otherwise the queue is empty. Scripts that a plugin enqueues only for a specific template or query will not show up this way. For a per-page view, the Query Monitor plugin lists the enqueued scripts of the page you are looking at. From that list, mark the ones that page does not need.
Common offenders:
- Contact Form 7 loads its CSS and JavaScript on every page, but the forms usually live on one or two pages. Fix: load it conditionally, only where a form exists
- WooCommerce cart fragments - the cart fragments AJAX runs on every page, even outside the shop. Fix: disable it on pages that are not part of the store
- Gutenberg block styles - WordPress loads CSS and JavaScript for all registered blocks, even ones not used on the page. Fix: strip the styles of unused blocks
Conditional loading can be done in the theme’s functions.php or with an asset manager plugin such as Asset CleanUp or Perfmatters.
3. Break long tasks
Any JavaScript task over 50ms blocks the main thread. While it runs, the browser cannot respond to user input, which directly raises the input delay part of INP. The fix is to split long tasks into smaller pieces and hand control back to the browser between them.
scheduler.yield() is the modern API. It is not available in every browser yet (the web.dev long tasks guide lists Chrome and Edge 129 and Firefox 142, with Safari still missing), so wrap it with a setTimeout fallback and yield on a deadline:
// Yield to the browser, with a fallback for browsers without scheduler.yield()
function yieldToMain() {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}
// Break a long loop into chunks, yielding about every 50 ms
async function processItems(items, deadline = 50) {
let lastYield = performance.now();
for (const item of items) {
processItem(item);
if (performance.now() - lastYield > deadline) {
await yieldToMain();
lastYield = performance.now();
}
}
}Older guides gated the yield behind navigator.scheduling.isInputPending(). web.dev no longer recommends that API: it can return false even after the user has interacted, and input is not the only reason to yield, because animations and other interface updates also need the main thread. Yielding on a time budget, whether input is pending or not, keeps the page responsive without relying on it.
4. Use passive event listeners
Scroll and touch event listeners can block the browser, because it has to wait and see whether the handler calls preventDefault() to cancel the scroll. Marking the listener as passive tells the browser that scrolling will not be cancelled, so it can scroll right away:
// Before: blocking
element.addEventListener('touchstart', handler);
// After: passive (tells browser scrolling won't be prevented)
element.addEventListener('touchstart', handler, { passive: true });
// Apply the same to scroll listeners
window.addEventListener('scroll', onScroll, { passive: true });This matters most on mobile, where touch events are constant. Many WordPress plugins add scroll listeners without the passive option, which causes jank and delayed interactions.
5. Optimize DOM size
Large DOM trees slow down every interaction because the browser must recalculate styles and layout for more elements. The web.dev article on DOM size and interactivity says Lighthouse treats a DOM as excessive above 1,400 nodes and starts warning at 800. On WordPress sites built with page builders, DOMs well above that are common, and good INP becomes very hard to reach.
Ways to reduce the DOM:
- Remove unnecessary wrapper divs - page builders like Elementor add several wrappers for every section and column. web.dev names reducing DOM depth as the main lever
- Lazy-load content below the fold - add elements to the DOM only when they are needed or become visible
- Use virtual scrolling for long lists - instead of rendering hundreds of items, render only the visible ones
- Simplify page builder layouts - fewer sections, columns and nested widgets
6. Eliminate jQuery where possible
jQuery adds about 85KB of minified JavaScript and blocks the main thread during initialization. Many themes and plugins use it only for simple operations that modern JavaScript handles natively:
// jQuery: document.ready
$(function() { /* ... */ });
// Modern: DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => { /* ... */ });
// jQuery: selector
$('.my-class');
// Modern: querySelectorAll
document.querySelectorAll('.my-class');
// jQuery: add a class
$('.element').addClass('active');
// Modern: classList
document.querySelector('.element').classList.add('active');
// jQuery: AJAX
$.ajax({ url: '/api/data', success: callback });
// Modern: fetch
fetch('/api/data').then(res => res.json()).then(callback);Removing jQuery entirely is not always possible, especially when essential plugins depend on it. In that case the priority is to load jQuery with defer and make sure the theme’s own code uses the modern alternatives.
Web Workers, content-visibility and requestIdleCallback for INP
Web Workers for heavy computation
CPU-intensive operations such as data processing, image manipulation or complex calculations should move off the main thread into a Web Worker, which keeps the main thread free to respond to the user:
// main.js - offload processing to worker
const worker = new Worker('heavy-task.js');
worker.postMessage(data);
worker.onmessage = (e) => updateUI(e.data);// heavy-task.js - runs on a separate thread
self.onmessage = (event) => {
const result = heavyComputation(event.data);
self.postMessage(result);
};The worker runs on its own thread, isolated from the main thread. While the calculation is running, the user can keep interacting with the page without delay. This is especially useful for WooCommerce stores with complex product filters or live search.
Content visibility for off-screen content
The CSS content-visibility property tells the browser not to render sections that are not in the viewport. That cuts rendering work during interactions, because the browser does not recalculate styles and layout for content the user cannot see yet:
/* Skip rendering for sections below the fold */
.below-fold-section {
content-visibility: auto;
contain-intrinsic-size: 0 500px;
}contain-intrinsic-size sets an estimated size for the section so the scrollbar does not jump. The technique works best on long pages with many sections, such as landing pages built with page builders.
requestIdleCallback for non-urgent work
Work that does not have to run immediately, such as loading analytics, prefetching resources or sending telemetry, can wait until the browser is idle:
// Load analytics during idle time
requestIdleCallback(() => {
loadGoogleAnalytics();
}, { timeout: 3000 }); // fallback: load within 3 seconds
// Prefetch the next page during idle time
requestIdleCallback(() => {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = '/next-page/';
document.head.appendChild(link);
});The timeout option guarantees the task runs within a maximum period even if the browser never becomes fully idle. It balances responsiveness (not blocking interactions) against function (making sure analytics still loads).
Does headless WordPress improve INP
For sites where INP is critical and optimising classic WordPress is not enough, a headless architecture is the more radical option.
INP on Astro sites
Astro sends zero JavaScript by default. On pages with no interactive islands, INP doesn’t apply because there’s nothing blocking the main thread. The browser receives plain HTML and CSS, so native interactions respond immediately.
Even on pages with interactive components, Astro uses the islands pattern: only the components that need interactivity load JavaScript. An add-to-cart button loads its own script, while the header, text, images and footer stay static HTML.
INP with Next.js and React Server Components
Next.js with React Server Components renders UI on the server and sends only the necessary client JavaScript. Combined with automatic code splitting, each page loads only the JavaScript it needs, not the whole application bundle.
In the Next.js App Router, components are Server Components by default. Only components explicitly marked with 'use client' send JavaScript to the browser. That inversion of the traditional model produces much smaller JavaScript bundles.
Typical INP by site architecture
| Approach | Typical INP | JavaScript Loaded |
|---|---|---|
| Traditional WordPress + plugins | 300-800ms | 500KB-2MB |
| Optimized WordPress (deferred scripts) | 150-300ms | 200-500KB |
| Next.js (App Router + RSC) | 50-150ms | 50-200KB |
| Astro (static + islands) | 0-50ms | 0-50KB |
These numbers come from real-world audits across dozens of WordPress sites and their headless counterparts. The pattern is consistent: reducing client-side JavaScript is the single most reliable way to lower INP. A WooCommerce store that migrated from a traditional Elementor-based theme to a Next.js frontend saw INP drop from 620ms to 89ms - a 7x improvement - while maintaining identical functionality. The key factor was eliminating 1.4MB of frontend JavaScript that the page builder and its associated plugins injected on every page load. Similarly, a content-heavy publisher running 15+ WordPress plugins switched to Astro with selective hydration and achieved INP under 30ms on article pages that previously scored 400ms+. The reduction in main thread blocking time directly correlated with a 12% increase in pages-per-session and a measurable drop in bounce rate within the first month.
WordPress INP optimization checklist
- Check Search Console Core Web Vitals for current INP scores
- Identify pages with poorest INP using PageSpeed Insights
- Defer all non-critical JavaScript (
deferorasync) - Conditionally load plugin scripts only on pages that need them
- Remove or replace jQuery-dependent code where possible
- Add
{ passive: true }to scroll/touch event listeners - Break long tasks with
scheduler.yield()orsetTimeout - Lazy-load below-fold content and third-party embeds
- Reduce DOM size below the Lighthouse threshold of 1,400 elements
- Consider migration to Astro or Next.js for the most dramatic improvement
- Monitor field INP in Search Console for 28 days after changes
Conclusion
INP is the Core Web Vital that separates fast-feeling sites from sluggish ones. While LCP measures loading and CLS measures stability, INP measures how the site feels when you interact with it. It is the metric closest to how fast a site actually feels: a page that loads quickly but takes half a second to answer a click still feels slow.
For WordPress sites, the path to good INP requires disciplined JavaScript management - or a fundamental architecture shift to Headless. Every plugin added, every third-party script included and every unoptimised event handler adds to main-thread blocking. The fixes in this guide (deferring scripts, conditional loading, breaking long tasks, passive listeners and a smaller DOM) are how a site moves from poor INP (over 500ms) to good INP (200ms or less).
The investment pays off directly in search rankings, user engagement, and conversion rates. Every millisecond of INP improvement makes your site feel more responsive, and Google rewards that with better visibility.
Start with measurement: pull up your Search Console Core Web Vitals report and identify the pages with the worst INP scores. Prioritize pages with the highest traffic first - fixing INP on a page that gets 10,000 visits per month has more impact than perfecting a page with 50 visits. Run a JavaScript audit on each problem page using the DevTools Performance panel, and work through the fixes in this guide from top to bottom. Deferring scripts and removing unused plugin JavaScript will typically get you 50-70% of the way to a passing score. For the remaining gap, focus on breaking long tasks and optimizing event handlers. If your INP remains stubbornly above 200ms after exhausting these fixes, evaluate whether a headless architecture makes sense for your use case - the JavaScript reduction is often the only path to consistently sub-100ms INP on complex WordPress sites.
Need help optimizing your Core Web Vitals? Explore our WordPress speed optimization services or contact us for a free performance assessment.






