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.
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.

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
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.
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)

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.

- Copy the Request URL from Network Waterfall (e.g., https://old-app.com/script.js)
- Go to Shopify Admin > Online Store > Themes > Actions > Edit Code
- 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
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.