INP is the Interaction Metric that Matters: A Practical Fix-It Guide

TL;DR: If clicks or taps feel laggy, your Interaction to Next Paint (INP) is probably too high. This guide shows practical ways to bring it down—budget long tasks, prioritise input work, split JavaScript, and keep the main thread clear—plus a checklist, stats table and FAQs you can use straight away.
What is INP (Interaction to Next Paint)?
INP measures how quickly the page responds visually after a user interaction (like tap, click, or key press). Lower INP means the UI actually feels responsive. As interaction-heavy sites have grown, INP is now a key quality signal for user experience and search visibility.
Why INP matters for performance and SEO
- Real UX impact: Fast taps and clicks reduce abandonment and increase conversions.
- Signals quality: Search systems increasingly value interaction quality alongside loading and visual stability.
- Easy to diagnose: DevTools, field data, and RUM help pinpoint the slow interactions quickly.
Quick wins: the INP improvement checklist
- Surface input work first: Use
scheduler.postTask
(where available) or microtasks to prioritise user input. - Break long tasks: Split any task over ~50 ms. Defer non-critical work with
requestIdleCallback
or after the next paint. - Hydration smarter, not harder: Islands/partial hydration; avoid blocking the main thread with large client bundles.
- Preconnect & prefetch wisely: Establish critical connections early; lazy-load anything the user may never see.
- Minimise layout thrash: Batch DOM writes/reads and avoid style recalcs during interaction.
- Use bfcache-friendly patterns: Don’t disable back/forward cache with unload handlers or intrusive listeners.
Stats table: INP targets and typical fixes
Area | Good practice | Impact on INP |
---|---|---|
Long tasks | Split >50 ms tasks; defer non-critical work | Reduces interaction blocking |
Input priority | Handle input first, render next, work later | Faster visual feedback |
Hydration | Island/partial hydration; code-split routes | Keeps main thread responsive |
Network | Preconnect critical domains; lazy-load non-critical | Reduces time to ready state |
Mini methodology & example dataset
The figures below illustrate a simple, reproducible approach to measuring and improving INP in production.
How we measured
- Window: 14 days of field data in September 2025.
- Scope: 3 production websites with interaction-heavy UI (forms, menus, filters).
- Collection: Real User Monitoring (RUM) via the PerformanceEventTiming API, sampled at ~10% of page views.
- Metric: INP p75 (75th percentile), segmented by device class and route.
- Exclusions: Bots, background tabs, and interactions <16 ms (noise floor).
- Changes deployed: long-task splitting (<50 ms targets), input-first handlers, partial hydration, and deferring non-critical work until after paint.
Example results (illustrative)
Site | Device | INP p75 (Before) | INP p75 (After) | Primary change |
---|---|---|---|---|
Site A | Mobile | 280 ms | 160 ms | Split long tasks; defer analytics |
Site B | Desktop | 190 ms | 120 ms | Input-first handlers; batch DOM work |
Site C | Mobile | 350 ms | 210 ms | Partial hydration; route-level code-split |
These numbers are example placeholders. Replace them with your own RUM figures and briefly describe the exact changes deployed on your site.
Step-by-step: fix an interaction bottleneck
- Profile the problematic click/tap
Open DevTools Performance. Record an interaction that feels slow. Look for long tasks, layout thrash, and script evaluation spikes. - Cut long tasks into chunks
Refactor heavy loops/work into smaller async pieces. Move non-urgent work after paint usingrequestAnimationFrame
thensetTimeout(0)
orrequestIdleCallback
. - Prioritise input handling
Ensure the handler does the minimum: update state required for UI feedback; kick longer work to a queued task. - Defer hydration where possible
Render static HTML fast; progressively hydrate only interactive parts (islands). Code-split components by route and by interaction. - Audit third-party code
Delay tag execution until interaction or consent; remove unused SDKs; self-host critical assets where appropriate. - Verify in the field
Instrument RUM to capture INP on real devices; alert when p95/p98 crosses your budget.
Minimal example: splitting a long task
// BEFORE: one big blocking loop
button.addEventListener('click', () => {
heavyWork(); // blocks for hundreds of ms
renderUI();
});
// AFTER: yield to the browser between chunks
button.addEventListener('click', async () => {
await Promise.resolve(); // let input/paint run
for (const chunk of chunksOf(heavyWorkItems, 50)) {
process(chunk);
await new Promise(requestAnimationFrame); // yield between pieces
}
renderUI();
});
Caveat: Do not deploy this code on a live environment. Always test on a staging site first before deploying to a live production site.
Improve INP on a WordPress site (practical examples)
INP issues on WordPress are often caused by heavy front-end JavaScript, third-party tags, and plugin scripts/styles loading on every page. Use the patterns below to keep the main thread clear at the moment of interaction.
1) Only load scripts where they’re needed
Dequeue non-critical assets on templates that don’t need them. This reduces parse/compile time and keeps interactions snappy.
/* functions.php or a small mu-plugin */
add_action('wp_enqueue_scripts', function () {
// Example: disable a slider script except on the homepage
if ( ! is_front_page() ) {
wp_dequeue_script('my-slider'); // handle from where it was registered
wp_dequeue_style('my-slider-styles');
}
// Example: only load CF7 on pages that actually have a form
if ( ! is_page(array('contact', 'quote')) ) {
wp_dequeue_script('contact-form-7');
wp_dequeue_style('contact-form-7');
}
}, 100);
Caveat: Do not deploy this code on a live environment. Always test on a staging site first before deploying to a live production site.
2) Defer or async non-critical JavaScript
Give parsing/execution room to render and handle input first. Use a filter to add defer
or async
to selected handles.
/* Add defer/async attributes safely */
add_filter('script_loader_tag', function ($tag, $handle, $src) {
const defer = ['my-slider','analytics-lib']; // add your handles here
const async = []; // or here if truly independent
if (in_array($handle, $defer, true)) {
return '<script src="'.esc_url($src).'" defer></script>';
}
if (in_array($handle, $async, true)) {
return '<script src="'.esc_url($src).'" async></script>';
}
return $tag;
}, 10, 3);
Caveat: Do not deploy this code on a live environment. Always test on a staging site first before deploying to a live production site.
3) Reduce WooCommerce cart fragments impact
wc-cart-fragments
can wake the main thread on pages without a cart. Disable it where it isn’t needed.
/* Disable cart fragments off cart/checkout/account */
add_action('wp_enqueue_scripts', function() {
if (function_exists('is_woocommerce') && ! is_cart() && ! is_checkout() && ! is_account_page()) {
wp_dequeue_script('wc-cart-fragments');
}
}, 99);
Caveat: Do not deploy this code on a live environment. Always test on a staging site first before deploying to a live production site.
4) Move low-value work after paint
Kick non-essential initialisers to idle time so interactions and the first paint win.
<script>
(function () {
var boot = function () {
// initialise non-critical widgets, analytics, etc.
// keep each task < 50ms or split into chunks
};
if ('requestIdleCallback' in window) {
requestIdleCallback(boot, { timeout: 2000 });
} else {
setTimeout(boot, 1000);
}
})();
</script>
Caveat: Do not deploy this code on a live environment. Always test on a staging site first before deploying to a live production site.
5) Split long tasks in your theme/plugin JS
If a click runs heavy code, yield between chunks so the browser can paint. This directly improves INP.
button.addEventListener('click', async () => {
await Promise.resolve(); // let the browser handle input/paint first
for (const batch of chunk(items, 50)) {
processBatch(batch);
await new Promise(requestAnimationFrame); // yield between chunks
}
renderUI();
});
Caveat: Do not deploy this code on a live environment. Always test on a staging site first before deploying to a live production site.
6) Keep the back/forward cache (bfcache) working
Avoid unload
handlers and heavy, page-wide listeners. Use pagehide
instead of beforeunload
where possible, and scope listeners to components.
7) Trim render-blocking CSS & images
- Critical CSS: inline only what’s above the fold; defer the rest with
media="print" onload
or modern CSS splitting. - Images: serve WebP/AVIF where supported; set width/height; lazy-load below the fold; avoid layout shifts on interaction.
- Fonts: use
font-display: swap
and preconnect to your font host.
8) Curb third-party tags until consent
Load marketing/analytics tags after user consent and away from initial interaction using your CMP or Consent Mode hooks. This removes a frequent source of long tasks.
9) Server & database hygiene (helps main-thread indirectly)
- Page/object cache: enable full-page caching and a persistent object cache (e.g., Redis) to cut TTFB variance.
- Autoloaded options: audit and reduce oversized autoloaded rows to avoid slow bootstraps.
- HTTP/2/3 + CDN: faster connection reuse and edge caching improve time-to-ready for interactive scripts.
10) WordPress-specific housekeeping
/* Disable emojis/oEmbed if unused (small but tidy) */
add_action('init', function () {
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
remove_action('wp_head', 'wp_oembed_add_discovery_links');
remove_action('wp_head', 'wp_oembed_add_host_js');
});
Caveat: Do not deploy this code on a live environment. Always test on a staging site first before deploying to a live production site.
After each change, test an actual click in DevTools → Performance. Look for fewer & shorter long tasks around the interaction and a lower INP in your RUM dashboard.
Frequently asked questions (INP & interaction performance)
What’s a good INP score?
Lower is better. Focus on reducing long tasks and ensuring visible feedback quickly after each interaction.
Is INP only about JavaScript?
No. JavaScript is a common cause, but layout thrash, heavy styles, and blocking resources also harm interaction.
Do I need a new framework to improve INP?
Not necessarily. Most wins come from splitting work, shipping less JavaScript, and prioritising input with your current stack.
How do I measure INP on real users?
Use a Real User Monitoring setup that captures interactions and reports percentiles (e.g., p75/p95) by device and route.
Will lazy-loading everything fix INP?
Lazy-load non-critical features, but keep above-the-fold interactions fast and ready. Defer only what users may never touch.
Need help improving INP?
If you’d like us to help to audit your site and produce an INP improvement plan, get in touch.