All Guides
Technical Performance

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

  1. Open DevTools (F12)
  2. Go to Performance tab
  3. Enable “Web Keys” in settings
  4. Record while interacting with the page
  5. 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.