INP on Shopify: The Core Web Vital Fixing LCP Won't Save You From
Shopify INP fails where LCP passes. A field guide to variant pickers, cart drawers, predictive search, and the jQuery app handlers dragging your 75th percentile past 200ms.
Your Lighthouse score went from 54 to 92. The hero image is WebP, the critical CSS is inlined, the theme is on Dawn 15. You open Search Console the following month and the Core Web Vitals report still says "Needs Improvement." You scroll down. LCP is green. CLS is green. INP is yellow, sitting at 318ms on mobile.
This is now the normal shape of a Shopify performance problem. Since INP replaced FID as a Core Web Vital in March 2024, it has quietly become the hardest CWV to pass on the platform, and the one most weight-loss programs for page speed completely ignore. Field data from the Chrome User Experience Report puts Shopify's aggregate median INP around 153ms, which looks fine, until you remember Google scores at the 75th percentile and that the long tail of real stores (the Shero Commerce 2025 benchmark of 1,000 mid-market Shopify sites) shows only 48% of them passing all three Core Web Vitals on mobile. LCP optimization buys you a number you can screenshot. INP is the metric that decides whether the tap on "Add to Cart" feels like a button or like a suggestion.
This piece is the audit we run on client stores before touching a single liquid file. It covers what INP actually measures on Shopify specifically, the four offenders that blow up the 75th percentile on almost every store we see, and a diagnostic flow using Chrome DevTools plus PageSpeed Insights field data that will tell you exactly which script line is eating your interaction budget.
What INP actually measures, and why Shopify gets hit hardest
INP is not a single measurement. It is the slowest (or near-slowest) interaction across an entire page visit, measured from the moment a user taps, clicks, or presses a key to the moment the browser paints the next frame. Google thresholds it at 200ms for "good" and 500ms for "poor." That budget has to cover three distinct phases: input delay (how long the main thread was busy when the tap landed), processing duration (how long the event handlers take to run), and presentation delay (how long until the browser can paint the resulting frame).
Most performance advice collapses these into "reduce JavaScript," which is not wrong but is not actionable. On Shopify the three phases fail for three different reasons, and the reasons are platform-specific. Input delay fails because some third-party app is running a 180ms task when the user tapped. Processing duration fails because the theme's section rendering API re-renders a full HTML fragment synchronously. Presentation delay fails because the resulting DOM is large enough that layout + paint eats the remaining budget. A single variant click can touch all three.
The other reason Shopify is structurally disadvantaged: the 75th percentile rule punishes tail latency. If 60% of your users are on an iPhone 14 and they experience 80ms interactions, and 20% of your users are on a 3-year-old Android on spotty LTE and they experience 650ms interactions, your 75th percentile lives in that Android tail. LCP improvements rarely help that user, because their problem is CPU, not network. INP is the metric that audits your JavaScript tax on cheap hardware, and every Shopify theme carries that tax whether or not the merchant knows it.
Offender one: the variant selector re-renders the entire product form
The single most reliable INP failure on a Shopify product page is the variant picker. Open snippets/product-variant-picker.liquid in any Dawn fork and trace what happens when a shopper clicks a size radio. The form change event fires, assets/product-form.js intercepts it, constructs a fetch to the product URL with the new variant ID, waits for the response, parses out the relevant section HTML, and synchronously swaps the inner HTML of #ProductInfo. On a product with 80+ variants (common in apparel with size and color permutations), the page also ships the full variant catalog as a JSON blob inside <script type="application/json">, which gets re-parsed on init.
On a Moto G Power class device (the reference Google uses for mobile CrUX), we measure this interaction at 250 to 450ms on an unoptimized Dawn store. The processing duration alone is often 180ms because innerHTML on a section that contains an entire product form with reviews widget, Judge.me stars, a bundle app's insert, and a sticky add-to-cart triggers a full layout recalculation for the product section. The input delay is usually clean (variant clicks rarely land during a busy main thread) but processing and presentation together overshoot the 200ms ceiling.
Shopify shipped a fix for this in 2024 that almost no one has adopted. The platform now supports an option_values URL parameter in combination with the product_option_value Liquid object, which lets themes load only the variants relevant to the current selection rather than all permutations. Shopify's own documentation on supporting high-variant products walks through the implementation. If your theme still hydrates every variant in a single JSON dump on page load and re-renders the entire product info section on change, you are paying INP rent you do not have to pay. Moving the section swap inside a requestAnimationFrame and scoping the replacement to the price + inventory nodes rather than the full #ProductInfo container usually drops the interaction to 90 to 120ms without any theme rewrite.
Offender two: the AJAX cart drawer and the section rendering API trap
Every modern Shopify theme ships an AJAX cart drawer. The flow is universal: click add-to-cart, the product form posts to /cart/add.js, the theme then fetches the cart drawer section via the Section Rendering API (/cart?sections=cart-drawer), and the returned HTML is spliced into the DOM. The cart-drawer.js file defines a custom element with a renderContents method that does this splice. On Dawn and its descendants (Horizon, Impact, Refresh, most premium themes built on the same pattern), the method uses innerHTML.
Here is what that costs on a real store. Customer has three items in cart. You have Yotpo loyalty widgets rendering in the cart upsell slot (weight roughly 300 to 500KB with the gallery on), a Rebuy cross-sell carousel, a Klaviyo back-in-stock form stub that runs on drawer open, and a shipping calculator. The section fetch returns 40 to 80KB of HTML. The innerHTML assignment triggers script revival (inline scripts re-run), layout, paint, and then the jQuery plugins bundled inside the upsell apps re-bind their event handlers. We clock this interaction at 400 to 700ms INP on mid-tier Android.
The contrarian read: most people blame the cart drawer's size or the theme's JavaScript. Neither is the real issue. The real issue is that the section rendering API encourages synchronous innerHTML replacement, and innerHTML forces a style recalculation of the entire subtree on the next frame. The fix is not to rip out the drawer. It is to replace innerHTML with a DOM diff (even a naive one scoped to the line items, quantity, and subtotal nodes) and to yield to the browser between parsing the response and mutating the DOM using await scheduler.yield() or a manual requestIdleCallback. This is a twenty-line change inside cart-drawer.js. It has moved client stores from 520ms cart-add INP to 140ms without touching a single app.
Offender three: predictive search and the debounce that is not a debounce
Predictive search is the quietest INP killer because merchants almost never test it. The flow: user types "shi", predictive-search.js debounces for 150 to 300ms depending on the theme, fires a fetch to /search/suggest.json?q=shi&resources[type]=product,collection,article, receives a JSON payload (or HTML in some themes using /search/suggest?section_id=predictive-search), and swaps the results panel. Every keystroke after the debounce window kicks off a new request, and the handler that processes the response is not debounced.
The first problem is that 150ms is too aggressive on a mobile keyboard. Users tap characters 60 to 120ms apart; the debounce fires between every second or third keystroke, meaning the main thread is repeatedly busy with response processing exactly when the next keystroke arrives. That is pure input delay. The second problem is that the response handler frequently does a full innerHTML swap of a results panel that includes product images (which then trigger image decode work off the main thread but visible in the presentation delay). We measure predictive search keystrokes at 220 to 380ms INP on unoptimized themes.
The fix is not one thing but a stack: push the debounce to 250ms (users will not notice), abort in-flight requests on new keystrokes with AbortController, and diff the results list rather than replacing it. Shopify's own UX guidelines for predictive search mention none of this, because the guidelines are about UX copy, not performance. If you implement the three changes above, predictive search typically drops to 80 to 110ms INP even on low-end Android.
Offender four: popup timers and the ghost of jQuery past
Here is the contrarian take the top three SERP results for "shopify INP" will not give you: most Shopify INP failures are not caused by React, Hydrogen, or the theme's own code. They are caused by jQuery event handlers still living inside third-party app embeds that were written in 2018 and never refactored.
Open any Shopify store running Klaviyo, Privy, Justuno, or one of the older popup apps. View source on the page. You will find inline scripts that use jQuery(document).ready(...) and bind delegated handlers to $(document).on('click', ...). Those delegated handlers run synchronously on every click anywhere on the page, walking the DOM to check selector matches. A single such handler adds 8 to 15ms to every interaction. Three apps doing this turns a clean 80ms interaction into a 125ms interaction before the theme has done anything.
On top of this, the popup apps themselves fire on a setTimeout or a scroll listener. When the timer expires and the popup renders, the first click after the popup shows is often 180 to 300ms because the popup library is measuring viewport, injecting inline CSS, and running its own A/B test hydration. Klaviyo's onsite JavaScript in particular is documented as adding 150ms or more to page load, and its form handlers attach on DOMContentLoaded, meaning any click before Klaviyo is done is held up behind it. The Core Vitals Fixer blog has a good teardown of the specific Klaviyo cost profile.
The fix here is uncomfortable because it is organizational rather than technical: audit the app stack, remove the three apps the merchant never uses, and for the apps that stay, load their script tags with the defer attribute and wrap their init calls in requestIdleCallback. We typically find four to six apps on a mid-market Shopify store that can be removed outright. Each removal is worth 20 to 60ms of INP budget at the 75th percentile.
The diagnostic flow: PageSpeed Insights then DevTools, in that order
Running Lighthouse and calling it a day will not find your INP problem. Lighthouse is a lab test; INP is a field metric. The diagnostic flow has to start with PageSpeed Insights field data.
Open PageSpeed Insights, enter the URL of the worst offender (start with the product page, then cart, then search results), and look at the "Origin Summary" and "This URL" sections at the top. If the URL-level data shows INP above 200ms on mobile, that is your working ticket. The field data is a 28-day rolling aggregate from real Chrome users, so it reflects the 75th percentile of actual interactions. Note which page type is failing and which device class. If mobile fails and desktop passes, you are dealing with a CPU problem, not a network problem, and the fix is JavaScript reduction.
From there, move to Chrome DevTools. Open the page on a throttled profile (CPU 4x slowdown, Fast 4G) in an incognito window so extensions do not pollute the trace. Open the Performance panel and start recording. Perform the interaction that is failing in field data: tap a variant, add to cart, type into predictive search. Stop the recording. Look at the Interactions track at the top of the trace. Any interaction over 200ms gets a red triangle in the top-right of its block. Click it. The bottom panel now shows the component breakdown: Input Delay, Processing Duration, Presentation Delay. This tells you which phase to attack.
The next move is the one most people miss. Enable Long Animation Frames in the Performance panel (Chrome 131+ exposes it via the extensibility API, or install the Web Vitals extension in a supporting browser). LoAF attribution shows which specific script file and line contributed to the frame. You will frequently see a Klaviyo script, a Yotpo handler, or a jQuery delegate inside an app embed eating 60 to 120ms. That is your actionable line. Screenshot it, match it to the app in your Shopify admin, and you now have the case for removing or deferring that app.
For continuous monitoring, install the web-vitals library v4 with attribution and send the loAFs, longestScript, and eventTarget fields to your analytics endpoint. This is the only way to know whether the user who experienced a 680ms interaction did so because of the cart drawer or because of a Klaviyo form handler. Field attribution closes the loop between Google's CrUX report and your fix queue.
The ceiling on DIY and where an audit actually helps
Most of what is in this article a technical founder can run themselves with a free afternoon, DevTools, and the willingness to read cart-drawer.js. The wall people hit is not knowledge. It is time and pattern recognition. On the last twelve Shopify stores we audited, eleven had the same three offenders in a different order: a variant picker re-rendering too aggressively, a cart drawer using innerHTML, and between four and seven third-party apps each contributing 15 to 80ms of handler time to every interaction. The twelfth was running a headless Hydrogen setup and had a different class of problem entirely, which is a different article.
If you want the short version: pull your PageSpeed Insights report for the product, cart, and search pages, record the failing interaction in DevTools Performance with LoAF attribution on, and look at which script owns the longest entry. Ninety percent of the time the fix is in the theme's own section rendering code or in an app you could live without.
If you want the long version, which is the audit where we rewrite the three offenders, trim the app stack, and give you a 75th percentile INP under 180ms with a before-and-after field data comparison thirty days later, that is what a WitsCode Shopify performance audit is. It takes about two weeks, it is fixed-scope, and it is almost always the highest-ROI technical work a Shopify store can commission between six and eight figures in revenue. If the INP number in your Search Console report has been yellow for three months and you are tired of looking at it, that is the next step. Chaos to code to clarity, in that order.
Get weekly field notes.
Practical writing on shipping products, straight to your inbox. No spam.
Need help with this?
Shopify Development
We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.
Talk to usWant to discuss ecom for your business?
Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.

