Fix INP Issues
Your site feels slow when people click things. Here’s why.
What this covers: What Interaction to Next Paint (INP) measures, why it replaced FID as a Core Web Vital, how to diagnose which interactions are slow, and how to fix common causes like main-thread JavaScript blocking, third-party scripts, and inefficient event handlers.
Who it’s for: Developers and site owners with INP scores above 200ms who need to improve interaction responsiveness for better Core Web Vitals.
Key outcome: You’ll identify the specific interactions causing poor INP scores and apply targeted fixes — deferring scripts, breaking up long tasks, optimizing event handlers — to bring your INP under 200ms.
Time to read: 12 minutes
Part of: Technical Performance series
Improve interaction responsiveness.
How fast does your site respond when someone actually clicks something?
Remember FID (First Input Delay)? Google killed it in March 2024. It only measured the first click, which was basically useless for understanding the actual user experience.
INP—Interaction to Next Paint—measures something much more useful: every interaction during your session. Clicks, taps, keyboard inputs. All of them. Google takes the worst one (actually the 98th percentile) and uses that as your score.
Why does this matter? Because users don’t just click once. They scroll, click buttons, fill out forms, navigate. If your site is responsive on the first tap but sluggish on the fifth, FID would’ve given you a pass. INP won’t.
The Numbers That Matter
| Score | What It Means | What Users Experience |
|---|---|---|
| Under 200ms | Good | Interactions feel instant |
| 200ms – 500ms | Needs improvement | Noticeable lag |
| Over 500ms | Poor | “Did that click even work?” frustration |
Google made INP a ranking factor in March 2024. It’s one of the three Core Web Vitals, alongside LCP (loading) and CLS (visual stability).
What INP Actually Measures
When you click a button, INP tracks:
- Input delay: How long until the browser starts processing your click
- Processing time: How long the JavaScript actually takes to run
- Presentation delay: How long until the screen updates to show the result
All three combined = your INP for that interaction.
What triggers INP measurement:
- Clicks (mouse or touch)
- Taps
- Keyboard inputs (including typing in forms)
What doesn’t count:
- Scrolling (has its own metrics)
- Hover effects
- Interactions where you scroll first, then release
Why Your INP Is Bad (Usually JavaScript)
Most INP problems come from one thing: JavaScript blocking the main thread. When JavaScript is executing, the browser can’t respond to clicks.
The Main Thread Problem
The browser has one main thread that handles:
- JavaScript execution
- User input processing
- Screen rendering
If your analytics script is crunching data for 400ms, any click during that time has to wait. That’s bad INP.
Common Culprits
Third-party scripts running wild:
- Analytics packages
- Chat widgets
- Social media embeds
- Ad tracking
- Customer review widgets
Your own code doing too much:
- Large framework hydration (React, Vue)
- Complex state updates
- Heavy DOM manipulation
- Synchronous data processing
How to Fix It
1. Break Up Long Tasks
JavaScript tasks over 50ms are considered “long.” Break them up so the browser can respond to clicks between chunks.
// Bad: One giant task
function processAllTheData(items) {
items.forEach(item => processItem(item)); // Blocks for 300ms
}
// Good: Yield to the browser between chunks
async function processAllTheData(items) {
const chunkSize = 50;
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
chunk.forEach(item => processItem(item));
// Let the browser handle any pending clicks
await new Promise(resolve => setTimeout(resolve, 0));
}
}
// Even better—use scheduler.yield() if available (Chrome 115+)
async function processWithYield(items) {
for (const item of items) {
processItem(item);
// Modern: yield to main thread
if (scheduler.yield) {
await scheduler.yield();
} else {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
}
2. Defer Non-Critical JavaScript
Don’t load everything at once. Defer scripts that aren’t needed for initial interaction.
<!-- Load immediately (needed for interactivity) -->
<script src="critical.js"></script>
<!-- Defer until page is parsed -->
<script defer src="non-critical.js"></script>
<!-- Load only when needed -->
<script>
document.querySelector('.show-video').addEventListener('click', async () => {
const { initVideo } = await import('./video-player.js');
initVideo();
});
</script>
3. Improve Event Handlers
Keep click handlers fast. Move heavy processing out of the handler.
// Bad: Heavy processing in click handler
button.addEventListener('click', () => {
const data = heavyCalculation(); // Blocks INP
updateUI(data); // More blocking
sendToAnalytics(data); // Even more
});
// Good: Immediate feedback, defer heavy work
button.addEventListener('click', () => {
// Immediate: Show user something happened
button.classList.add('loading');
// Defer: Heavy stuff happens after paint
requestAnimationFrame(() => {
setTimeout(() => {
const data = heavyCalculation();
updateUI(data);
sendToAnalytics(data);
button.classList.remove('loading');
}, 0);
});
});
4. Use CSS for Visual Feedback
CSS transitions don’t block the main thread. Use them for immediate feedback.
.button {
transition: transform 0.1s ease, opacity 0.1s ease;
}
.button:active {
transform: scale(0.98);
opacity: 0.9;
}
.button.loading {
pointer-events: none;
opacity: 0.6;
}
5. Audit Third-Party Scripts
Each third-party script is a potential INP killer. Audit what’s actually running.
Chrome DevTools → Performance → Record a session → look for long tasks
Common offenders:
- Google Tag Manager (if misconfigured)
- Facebook Pixel
- HotJar/Lucky Orange session recording
- Chat widgets (Intercom, Drift)
- Multiple analytics packages
Fix options:
- Remove scripts you don’t actually use
- Load scripts after user interaction instead of on page load
- Use lighter alternatives (Plausible instead of Google Analytics, etc.)
- Implement consent-based loading (only load after cookie acceptance)
WordPress-Specific Fixes
WordPress sites often have INP problems because plugins add JavaScript without coordination.
// Add to functions.php
// Defer non-critical scripts (WordPress 6.3+)
add_action('wp_enqueue_scripts', function() {
$defer_these = ['contact-form-7', 'comment-reply', 'wp-embed'];
foreach ($defer_these as $handle) {
wp_script_add_data($handle, 'strategy', 'defer');
}
});
// Only load Contact Form 7 on pages that use it
add_action('wp_enqueue_scripts', function() {
if (!is_page('contact')) {
wp_dequeue_script('contact-form-7');
wp_dequeue_style('contact-form-7');
}
}, 100);
Plugin recommendations:
- WP Rocket: Has delay JavaScript execution feature
- LiteSpeed Cache: Built-in script delay options
- Flying Scripts: Specifically designed for deferring third-party scripts
How to Measure INP
PageSpeed Insights
pagespeed.web.dev shows your INP score. If you have enough traffic, it shows “field data” (real user scores). Otherwise, you get “lab data” (simulated). Field data matters more for rankings.
Chrome DevTools
- Open DevTools (F12)
- Go to Performance tab
- Enable “Web Keys” in settings
- Record while interacting with the page
- Look for “INP” markers
Real User Monitoring
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric.value, 'ms');
// The interaction that had the worst INP
console.log('Interaction type:', metric.entries[0]?.name);
// Send to your analytics
gtag('event', 'web_vitals', {
event_category: 'Performance',
event_label: 'INP',
value: Math.round(metric.value)
});
});
Google Search Console
Search Console → Core Web Vitals report shows your INP scores for real users. This is what Google uses for rankings.
What About FID?
FID (First Input Delay) was the old metric. It only measured the first interaction, which had two problems:
- Users don’t interact once. They click multiple things during a session.
- The first click is often fine. JavaScript typically loads more stuff as users interact, making later interactions slower.
If you have old monitoring showing FID scores, those are historical curiosities. INP is what matters now.
FID thresholds (historical):
- Good: Under 100ms
- Poor: Over 300ms
INP thresholds (current):
- Good: Under 200ms
- Poor: Over 500ms
Note that INP is more lenient on the thresholds because it’s measuring more of the interaction cycle.
Quick Wins
- Add defer to all non-critical scripts (analytics, widgets, tracking)
- Remove unused plugins/scripts (audit what’s actually running)
- Add CSS :active states for immediate click feedback
- Check PageSpeed Insights to see your current INP
- Look at Chrome DevTools Performance tab to find long tasks
The Checklist
Before you call it done:
- INP under 200ms on mobile (PageSpeed Insights)
- No JavaScript tasks over 50ms (DevTools Performance tab)
- Third-party scripts audited and minimized
- Click feedback is instantaneous (CSS, not JS-dependent)
- Heavy processing deferred with setTimeout or requestIdleCallback
INP Questions Answered
What is INP and why does it matter?
INP (Interaction to Next Paint) measures how quickly your page responds to user interactions like clicks, taps, and key presses. It replaced FID as a Core Web important in March 2024. Poor INP makes sites feel sluggish and unresponsive.
What is a good INP score?
Good: under 200 milliseconds. Needs improvement: 200-500ms. Poor: over 500ms. Unlike LCP, INP measures ALL interactions during the page visit and reports the worst one (at the 75th percentile).
What causes poor INP?
Long-running JavaScript that blocks the main thread. Common culprits: heavy analytics scripts, chat widgets, third-party ads, and complex React/Vue re-renders. Any JavaScript that takes more than 50ms to execute can cause INP issues.
How do I identify INP problems?
Chrome DevTools → Performance panel → record interactions → look for long tasks (red bars). The Web Keys extension shows real-time INP. PageSpeed Insights shows field data from real users over 28 days.
Sources
- Interaction to Next Paint (INP) – Official Google definition
- Improve INP – Google’s improvement guide
- INP replaces FID announcement – Google Search Central
- web-vitals library – For real user monitoring
Confirming Your INP Score Is Green
- PageSpeed Insights shows INP under 200ms
- Web Keys extension shows green INP during typical page interactions
- Search Console’s Core Web Vitals shows “Good” for Interaction to Next Paint
- Clicking buttons and links feels instant with no perceptible delay
Test it: Click around your site on mobile. If anything feels sluggish, there’s more work to do. Trust your fingers—they notice delays your metrics might miss.
✓ Your INP Issues Are Fixed When
- Interaction to Next Paint is under 200ms (good) in Chrome UX Report or PageSpeed Insights field data
- No JavaScript event handler blocks the main thread for more than 50ms (verify in Chrome DevTools Performance tab)
- Click, keypress, and tap interactions all produce visual feedback within 200ms on a throttled 4x CPU slowdown test
- Third-party scripts are loaded with async/defer and don’t block interaction handlers
Test it: Open Chrome DevTools, go to Performance, enable the “Web Vitals” track, click through your 5 most interactive pages, and confirm no interaction exceeds the 200ms INP threshold.