Shopify INP Optimization: 4 Fixes That Move The Score

Altin Gjoni

Written by Altin Gjoni

Content Strategist

Shopify INP Optimization: 4 Fixes That Move The Score

Your Shopify store's INP score determines how fast it feels, not how fast it loads. A store can score green on LCP and other core metrics and still feel sluggish for users, hurting conversions.

In our speed benchmark of 1,000 Shopify stores, the median INP was 153ms. That's technically passing. But the interactions that matter most, cart, filters, variant swaps, are often hiding a much worse number inside that average.

This guide will show you how to find your real INP, isolate the cause, and fix it without rebuilding your theme.

What are Core Web Vitals basics?

Core Web Vitals are a set of three specific, user-centric performance metrics developed by Google to measure a website's loading speed, interactivity, and visual stability. Those metrics are:

LCP (Largest Contentful Paint): How quickly does the main content, usually the largest visible image/text block, finish rendering
INP (Interaction to Next Paint): INP measures the delay between a user interaction and the next visual update that confirms the site responded. It's how the website feels to the user.
CLS (Cumulative Layout Shift): How visually stable the page is, how much the layout unexpectedly shifts while loading/using the page.

The core web vitals average pass rate of Shopify stores in your vertical

What does a slow INP mean?

A slow Interaction to Next Paint (INP) means your website is sluggish. While your site loads quickly, customers will still find it unresponsive when they try to add products to the cart or take other actions.


Check out Shopify's web performance reports for an in-depth look at the latest data

What is a good Shopify INP score?

A good INP is under 200ms. That threshold matters because it determines how your store feels to customers:

  • Add to Cart button responds instantly
  • Variant changes show price immediately
  • Site feels faster, especially on mobile

If your INP score is good (<200ms) but interactions still feel slow, test individual interactions in Chrome DevTools; your worst interactions might be hidden in the average score.

Shopify INP optimization testing checklist

Before you start testing or optimizing, refer to our checklist to keep you on track. Often, you need to test multiple times and go back and forth; thus, we included a change log in the document and detailed instructions.

Download the Shopify INP Optimization Checklist

A snippet from our Shopify INP optimization checklist

How to measure your Shopify INP score

PageSpeed Insights and Chrome DevTools measure INP. We'll use both to pinpoint exactly what's slowing your store.

Step 1. Diagnose INP with PageSpeed Insights (PSI)

The first tool we will use is PageSpeed Insights. Paste your store URL and run the test. The results can be easily read by referring to the following:

  • Good (Green): ≤ 200 milliseconds
  • Needs Improvement (Orange): 200-500 milliseconds
  • Poor (Red): > 500 milliseconds

The INP for this particular site is 267, not the worst, but far from optimal.

If you scroll down the results, a section that lists the website's performance issues. Not all of them relate to INP.

The table below shows how to interpret PSI diagnostic results for INP in the example above.

Priority PSI Warning Impact on INP Why It Hurts
🔴 Critical Reduce JavaScript execution time (4.4s) Blocks browser from responding to clicks Long JS tasks keep the main thread busy, delaying interaction processing
🔴 Critical Minimise main-thread work (6.9s) Browser queues interactions instead of processing them Heavy scripting, layout, and rendering block user input handling
🔴 Critical Reduce unused JavaScript (1,658 KiB) Steals main-thread time Unused code still gets parsed and compiled
🔴 Critical Use efficient cache lifetimes (495 KiB) Re-download + re-execution on every visit Scripts execute again instead of loading from cache
🔴 Critical Improve image delivery (732 KiB) Competes with interaction handling Heavy image decoding and resizing consume main-thread resources
🔴 Critical Legacy JavaScript (59 KiB) Adds execution overhead Polyfills and transpiled code increase parse + compile time
🔴 Critical Forced reflow Stalls rendering pipeline Synchronous layout reads pause main thread before next paint
🔴 Critical Font display (30ms) Delays visual feedback Render-blocking fonts delay paint after interactions
🟡 High LCP request discovery Slower first meaningful paint Late LCP resource discovery makes page feel unresponsive
🟠 Medium Optimise DOM size Slows style recalculation Large DOM increases layout + recalculation cost
🟠 Medium Third parties Compete for main-thread time Analytics, chat, pixels block interaction processing
🟠 Medium Reduce unused CSS (96 KiB) Bloats style recalculation Extra CSS increases recalculation cost on interaction

Step 2. Find the exact culprit in Chrome DevTools

PageSpeed Insights tells you what is slow. Chrome DevTools shows you exactly which interactions are causing problems and when they happen during a user's visit.

To use Chrome DevTools, open your store in Incognito mode > Inspect (or press F12 on Windows, Cmd+Option+I on Mac) > Click the "Performance" tab at the top of the DevTools panel > there you will see a record button.

Chrome DevTools Performance tab doesn't display an "INP score" like PageSpeed Insights does. Instead, it shows you the underlying problems that cause poor INP - specifically, long tasks that block interactions.

The purpose of this test is to record a normal user's interaction and let Chrome judge how fast or slow it was. Afterwards:

  • Click the record button in the Performance tab
  • Perform the interaction you want to test. Test the major customer touchpoints first: click "Add to Cart", open the mobile menu, select a product variant, apply a collection filter, and open the cart drawer.
  • Stop recording after you do the procedures (click the red ⏹ button)

The result shows an impressively well-optimized INP; however, upon deeper inspection, the timeline density suggests that other interactions are much slower.

Here's how to interpret what you're seeing:

Dense Main Thread Activity: The Main thread (middle section) shows almost no gray idle time; it's packed with yellow (JavaScript execution) and purple (rendering) blocks. This means the browser is constantly busy and can't respond to clicks immediately.

Multiple Long Tasks The thick yellow blocks throughout the timeline indicate tasks taking >50ms each. Every one of these creates a moment when user interactions freeze or queue up instead of being processed.

Heavy Third-Party Scripts The Network section (top) shows dozens of JavaScript files loading (yellow bars). On this checkout page, these likely include payment processors, fraud detection, analytics, and marketing pixels—all competing for main thread time.

Multiple User Interactions The diamond markers in the Interactions row show 4-5 clicks during this recording. While the selected one measured 62ms (good), you must check each interaction individually; later clicks often queue behind long tasks, resulting in 300-500ms delays.

It may feel overwhelming at first, but you'll find your footing as you go. Every one of these areas is worth your attention; they all make a meaningful difference and are part of any big brand's Shopify Optimization Strategy.

Why does the PSI and Chrome Dev Tools INP score change?

PSI and DevTools measure different things.

  • PSI measures real user data from the past 28 days (field data)
  • Chrome DevTools measures your single test session right now (lab data)

Both are used for different purposes. You should rely on PSI for performance, as it's more relevant to how Google ranks and to customer experience, while Chrome DevTools lets you pinpoint the problems.

How To Optimize Shopify INP: 4 Proven Fixes

Once you know where the delay is coming from, most INP problems on Shopify come down to four root causes. None of them requires a theme rebuild. Some require a small code change; others are fixed entirely from the Shopify admin.

What fix should I apply first to my website?

Start with the fix that matches your store's symptoms and setup. The table below outlines what to look for first in your store.

If Your Store Has... Start With... Expected Improvement
Klaviyo, Facebook Pixel, or Google Analytics Fix #1: Defer scripts 100-200ms
Product variants (sizes, colors) Fix #2: Optimize variant selector 150-300ms
Review apps (Judge.me, Loox, Yotpo) Fix #4: Lazy load reviews 50-100ms
Collection pages with 48+ products Fix #3: Lazy load images 100-200ms
Mega menu with lots of links Fix #4: Load menu on hover 80-150ms

Fix 1. Defer or remove third-party scripts

Third-party scripts, such as pixels, chatbots, and analytics tools, account for the majority of INP issues on Shopify stores. The 'bloat' caused by these unnecessary leftovers or heavy scripts needs sweeping.

First, we need to pinpoint which scripts take longer to load using the Network waterfall in Chrome DevTools.

Right-click the page in incognito mode, and head to Network to start the test.

  • If it comes from theme.liquid, snippet, or section, it's likely safe to remove if the app is deleted
  • If it's from an App loader or vendor CDN, then check App Embeds or app settings
  • Other third-party script removal must happen upstream, not in theme code

Do not delete code right away! You might fix the problems by disabling app embed in your Shopify admin. Comment the code out first, then test whether your site still works well.

  1. Copy the Request URL from Network Waterfall (e.g., https://old-app.com/script.js)
  2. Go to Shopify Admin > Online Store > Themes > Actions > Edit Code
  3. Use the search bar to search for the domain name or script filename

Enter the following lines to comment out the code. Only delete after testing the website after commenting!

If it's a Liquid snippet:

{% comment %}
  {% render 'app-name' %}
{% endcomment %}

If it's a script tag:

The same fix doesn't always work

Every action you can take based on what you see in the Network waterfall analysis is listed in the table below.

What You See in Network Waterfall What It Means What to Do
Script loads on every page, even unrelated ones Script injected globally via theme.liquid or App Embed Move to page-specific templates or disable App Embed for irrelevant pages
Script loads before DOMContentLoaded (blocking) Script is render-blocking, delaying all interaction readiness Add defer or async attribute to the script tag in theme code
Script takes >500ms to load from external CDN Slow CDN or large file; delays interaction response on first load Self-host the script if possible, or replace with lighter alternative
Multiple scripts from same vendor (e.g., 3 Meta files) Duplicate or redundant script injection from multiple locations Audit theme.liquid, app embeds, and GTM tags for duplicates and remove extras
Script loads after user interaction (on demand) Lazy-loaded script; may add delay on first use Accept the trade-off or preload if that interaction is critical
Script file size >200KB Bloated script that takes time to parse and execute Check if a lighter version exists or if the app is replaceable

Fix 2. Fix event handlers that block the main thread

Event handlers are pieces of JavaScript code that run when users interact with your site. If these handlers do too much work synchronously (all at once), the browser can't update the screen until they finish, causing visible INP delays.

There are three common scenarios, each requiring specific changes to the theme code.

1. Scroll listeners fire constantly and cause jank

If your theme runs heavy code on every scroll event, the browser can't keep up, and interactions (clicks, taps) get delayed while it's still processing the previous scroll.

The best fix is to avoid JavaScript for sticky headers. If your goal is just a sticky header, use CSS:

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
}

If you truly need JavaScript (e.g., change styles after a threshold): Throttle updates so your code runs at most once per animation frame:

let ticking = false;

window.addEventListener('scroll', () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      // your code here
      ticking = false;
    });
    ticking = true;
  }
});

2. Variant selectors run all updates synchronously

In this scenario, customers wait 300-500ms before anything on screen changes.

To fix it, head to Online Store > Themes > Edit Code > Search for "variant." You will find that most themes have a variant change handler that looks like this:

variantSelector.addEventListener('change', function() {
  updatePrice(selectedVariant);
  updateImages(selectedVariant);
  updateInventoryStatus(selectedVariant);
  updateShippingInfo(selectedVariant);
  updateAddToCartButton(selectedVariant);
  trackVariantChange(selectedVariant);
});

To speed things up, defer non-critical updates until after price and button state have resolved.

Change this:

// All updates run immediately
updatePrice(selectedVariant);
updateImages(selectedVariant);
updateInventoryStatus(selectedVariant);
updateShippingInfo(selectedVariant);
updateAddToCartButton(selectedVariant);
trackVariantChange(selectedVariant);

To this:

// Critical: runs immediately
updatePrice(selectedVariant);
updateAddToCartButton(selectedVariant);

// Deferred: runs after browser paints
requestAnimationFrame(() => {
  updateImages(selectedVariant);
  updateInventoryStatus(selectedVariant);
});

// Non-critical: runs when browser is idle
requestIdleCallback(() => {
  updateShippingInfo(selectedVariant);
  trackVariantChange(selectedVariant);
});

The result is that price updates instantly (40ms), images, which require their own separate optimization strategy, load shortly after, and analytics fire when the browser is idle.

3. Add to cart buttons show no immediate feedback

This causes double-clicks and frustration. Similar to the above, find "add-to-cart" or "cart/add" in the theme code.

Before (no feedback):

addToCartBtn.addEventListener('click', async function() {
  const response = await fetch('/cart/add.js', {
    method: 'POST',
    body: JSON.stringify({ id: variantId, quantity: 1 })
  });
  const cart = await response.json();
  updateCartCount(cart.item_count);
});

After (instant feedback):

addToCartBtn.addEventListener('click', async function() {
  // Immediate visual feedback (happens in <16ms)
  addToCartBtn.textContent = 'Adding...';
  addToCartBtn.disabled = true;

  const response = await fetch('/cart/add.js', {
    method: 'POST',
    body: JSON.stringify({ id: variantId, quantity: 1 })
  });
  const cart = await response.json();

  // Update after network response
  addToCartBtn.textContent = 'Added!';
  updateCartCount(cart.item_count);
});

What changed: Button text changes to "Adding..." immediately. User knows their click worked.

Fix 3. Break up long tasks on collection pages

On many collection pages, clicking a filter triggers a lot of main-thread work at once:

  • fetching a big HTML response
  • parsing it into the DOM
  • replacing the entire grid
  • recalculating layout/styles for many cards
  • kicking off image loads and "after render" scripts (badges, quick add, reviews, tracking)

This often creates long tasks (>50ms), which block the browser from painting updates. When the main thread is blocked, user clicks queue up, causing the perceived delay that hurts INP.

Here's how the code looks in a real Shopify store:

filterForm.addEventListener('submit', async function(e) {
  e.preventDefault();
  const filterValue = new FormData(this).get('filter');
  const response = await fetch(`/collections/all`, {
    headers: { 'X-Filter': filterValue }
  });
  const html = await response.text();
  const newDoc = new DOMParser().parseFromString(html, 'text/html');
  const newGrid = newDoc.querySelector('.product-grid');
  document.querySelector('.product-grid').innerHTML = newGrid.innerHTML;
  initQuickAdd();
  initReviews();
  trackFilterChange(filterValue);
});

The Fix: Progressive Loading

filterForm.addEventListener('submit', async function(e) {
  e.preventDefault();
  const filterValue = new FormData(this).get('filter');

  const response = await fetch(`/collections/all`, {
    headers: { 'X-Filter': filterValue }
  });
  const html = await response.text();
  const newDoc = new DOMParser().parseFromString(html, 'text/html');
  const products = [...newDoc.querySelectorAll('.product-card')];

  const grid = document.querySelector('.product-grid');
  grid.innerHTML = '';

  // Batch: insert 6 products at a time
  const batchSize = 6;
  for (let i = 0; i < products.length; i += batchSize) {
    await new Promise(resolve => setTimeout(resolve, 0));
    const batch = products.slice(i, i + batchSize);
    batch.forEach(p => grid.appendChild(p));
  }

  // Defer non-critical post-render work
  requestIdleCallback(() => {
    initQuickAdd();
    initReviews();
    trackFilterChange(filterValue);
  });
});

What changed: Products appear in batches of 6 instead of all at once. The browser can respond to clicks between batches, and non-critical scripts run when the browser is idle.

Fix 4. Reduce DOM size in high-interaction sections

DOM stands for "Document Object Model." It's basically all the HTML elements on your page. Every

, , and
  • is a DOM node. The more nodes you have, the more work the browser does every time someone interacts with the page.

    When you have too many elements, the browser struggles to:

    • Calculate where everything should be positioned
    • Update the page when users interact (click, scroll, type)
    • Respond to JavaScript changes

    Check your current DOM size

    The ideal DOM size is under 800. Anything above should signal that there's some cleaning up you can do. Open Inspect > Console, and type: document.querySelectorAll('*').length

    High-impact DOM reductions in Shopify:

    • Cart drawers: render only what's needed (avoid duplicating full product card markup inside the drawer).
    • Navigation menus: limit nested columns/links; load heavy submenus on tap/hover instead of all at once.
    • Collection pages: reduce product-card complexity and avoid injecting extra badges/widgets into every tile.
    • App blocks: remove blocks that add repeated markup across products or sections.

    You don't need to make the website smaller. Focus only on the sections customers interact with most.

    Speed is a process, not a one-off

    Every app install or theme update can introduce new scripts. Run these diagnostics quarterly, especially after Shopify theme updates or new app installations.

    If your INP is still above 200ms after applying these fixes, the problem is likely structural. Nested themes, bloated apps, or JavaScript-heavy customizations may require deeper intervention. Book a technical audit to get a complete picture of what's slowing your store down and a prioritized fix plan.

  • Will optimizing INP affect my Shopify theme warranty or support?

    No. The fixes in this guide don’t void your theme warranty. You’re changing how scripts load, not touching the theme’s core structure.

    That said, always duplicate your theme before making changes. If something breaks, you can restore the backup in two clicks.

    If you’re using a premium theme like Prestige or Symmetry, check the developer’s documentation before commenting out any code they specifically require. Most theme shops are fine with script optimization, they just want you to test first.

    How long does it take for INP improvements to show up in PageSpeed Insights?

    PageSpeed Insights field data updates every 28 days. After you apply these fixes, you’ll see immediate improvements in the “Lab Data” section, but the official green/orange/red score won’t change until real users visit your store over the next month.

    For faster feedback, use Chrome DevTools Performance tab. Those results reflect your changes immediately.

    Low-traffic stores might never accumulate enough data for PSI to show a field score. That’s normal. Focus on the Lab Data score and manual testing instead.

    My developer says INP optimization will break features. Is this true?

    INP optimization will break features in your Shopify store only if you delete code instead of deferring it. The fixes in this guide defer features, not remove them.

    For example, moving analytics to ‘setTimeout’ doesn’t break tracking. It delays the tracking call by 100ms, users never notice, but your INP improves by 70-100ms.

    Common concerns from developers who haven’t tested:

    • Analytics won’t track: Wrong. Deferred analytics still fire, just 100ms after the interaction instead of blocking it.
    • Lazy-loaded images won’t load: Wrong. Images load when users scroll near them. exactly how native browser lazy loading works.
    • Checkout will break: This one’s real. Never touch checkout scripts without extensive testing. Shopify’s checkout is heavily instrumented, and one wrong defer can block payments.

    Always duplicate your theme and test on a staging environment first. If you don’t have a staging site, use the theme preview mode before publishing.

    How does the INP score affect my Google Rankings?

    INP is part of Core Web Vitals, which Google confirmed as a ranking factor. It works as a tiebreaker between pages with similar content quality.

    While having a better INP than your competition doesn’t mean you will outrank them, it leads to a better user experience and higher conversions. Higher conversion rates mean visitors spend more time on the page.

    The combination of these factors is enough to signal to Google that the page is relevant and improve rankings.

    Altin Gjoni

    Content Strategist

    Altin Gjoni is a Content Strategist who creates in-depth, actionable content for Shopify and eCommerce merchants. With a background in digital strategy and hands-on experience across multiple industries, he turns complex eCommerce challenges into clear, practical guides that help brands grow, convert, and compete.