Shopify Core Web Vitals Checklist 2026
Practical, theme-level checklist for fixing LCP, INP, and CLS on Shopify stores. The same audit I run before quoting a performance engagement.
Most Shopify stores I audit are not slow because Shopify is slow. They are slow because of decisions made on top of Shopify — app stack, theme structure, third-party scripts, and image discipline. Liquid is fast; the problems sit one layer up.
This is the same audit I run on a paid engagement, in the order I run it. Work through it top to bottom; the early items move LCP the most.
Step 0 — Measure first, both lab and field
You need two data points before changing anything:
- Lab (synthetic): PageSpeed Insights. Run mobile + desktop for the homepage, a collection, and a product page. Note LCP, INP, CLS, TBT, performance score, and the “Opportunities” panel.
- Field (real users): Shopify Admin → Online Store → Themes → “View report” on the speed score, plus Google Search Console → Experience → Core Web Vitals. Field data lags ~28 days but it is the number Google ranks on.
Save those numbers. Every change you make should reference them.
Step 1 — Audit the app stack
This is almost always the biggest LCP win. Each installed Shopify app drops at least one script tag into your theme.
Inventory
In Shopify Admin → Apps, list every installed app. Then in theme.liquid and layout/theme.liquid, search for app blocks ({% render 'app...' %}), app embeds, and any script tags injected via app block.
For each app ask:
- Is it actively used? (Disable and watch sessions for 48h.)
- Is the script tag loading on every page or only the page that needs it?
- Does the app offer “defer until interaction” or “load only on cart”? Most modern Shopify apps do.
Common culprits
- Review apps loading reviews on the homepage just to show a “rated 4.9” badge. Replace the badge with a static image.
- Multiple analytics scripts (Shopify analytics + GA4 + Meta Pixel + TikTok Pixel + Klaviyo). Consolidate through Google Tag Manager with consent gating.
- Pop-up apps loading on first paint to render a modal that fires after 8 seconds. Defer.
- Heatmap apps (Hotjar, Mouseflow) — only sample 10–20% of sessions; load conditionally based on user agent.
Realistic target: each non-essential app costs 100–400 ms of JS execution on mobile. Removing five marginal apps typically buys you 1–2 seconds of LCP.
Step 2 — Image discipline
The LCP element on a Shopify homepage is almost always an image — the hero banner, the first product image, or the slideshow. The browser cannot paint LCP until that image is downloaded and decoded.
Hero image rules
- Width: ship the exact rendered width, not 2× or 3×. A 1440-px-wide hero rendered at 100vw on a 414-pt phone needs ~830 px (414 × 2 device pixel ratio).
- Format: WebP at quality 80 is typically 60–70% smaller than the equivalent JPEG. Shopify’s
image_urlfilter handles this if you passformat: 'webp'. - Priority hints: put
fetchpriority="high"andloading="eager"on the hero. Useloading="lazy"on everything below the fold. - Responsive
srcset: ship 3–4 sizes (480w, 800w, 1200w, 1600w) and let the browser pick.
Liquid example:
{% assign hero = section.settings.hero_image %}
<img
src="{{ hero | image_url: width: 1200, format: 'webp' }}"
srcset="
{{ hero | image_url: width: 480, format: 'webp' }} 480w,
{{ hero | image_url: width: 800, format: 'webp' }} 800w,
{{ hero | image_url: width: 1200, format: 'webp' }} 1200w,
{{ hero | image_url: width: 1600, format: 'webp' }} 1600w"
sizes="100vw"
width="1200"
height="600"
alt="{{ hero.alt | escape }}"
loading="eager"
fetchpriority="high"
decoding="async"
/>
Slideshow LCP trap
Slideshows are a classic LCP killer. The first slide is the LCP candidate, but slideshow apps often delay rendering it behind a JS init step. Two fixes:
- Server-render the first slide as a plain image; let the slideshow JS take over on hydration.
- Drop the slideshow. Conversion data rarely justifies one. If you must keep it, lazy-init via
requestIdleCallback.
Step 3 — Font loading
Headings on most stores are custom fonts. Until the font file arrives, headline text either invisible (FOIT) or styled with fallback (FOUT). LCP is gated on whichever the theme picks.
Quick wins
font-display: swapon every@font-face. Liquid theme files almost always have this; verify.- Preload the LCP-blocking font weight only. If your h1 is Sora 700, preload
sora-700.woff2. Do not preload all 6 weights — you waste bandwidth.
<link rel="preload" as="font" type="font/woff2"
href="{{ 'sora-700.woff2' | asset_url }}" crossorigin>
-
Self-host if you can. Google Fonts adds a DNS lookup + TLS handshake to
fonts.gstatic.comthat runs serially with everything else. Self-hosted fonts share the storefront connection. -
Subset. A 80 KB latin font becomes ~25 KB with proper subsetting. Tools: glyphhanger, fontkit. Or use Fontsource which ships subsets per-locale.
Step 4 — Third-party script discipline
Open DevTools → Network → Filter JS. Sort by size. Anything above 50 KB that is not the Shopify core bundle deserves scrutiny.
The “defer everything you can” rule
The browser blocks on synchronous scripts. Mark every non-critical script defer or async:
defer— runs in order, after HTML is parsed. Use for theme JS.async— runs whenever it downloads, no order guarantee. Use for analytics, pixels.
The exceptions that cannot be deferred:
- Shopify’s
Shopify.themepayload. - Anything that must run before LCP renders (rare — almost always wrong).
- A/B testing scripts that flicker if delayed (acceptable if delaying breaks the experiment; preferable to refactor the test).
Common heavy offenders (and substitutes)
| Heavy script | Lighter substitute |
|---|---|
| Klaviyo full bundle on every page | Klaviyo’s “On-site” stub, full bundle only on signup forms |
| jQuery + jQuery UI | Vanilla JS, or pull the 2-3 functions you use |
| Lottie player for one animated icon | A 4-line CSS animation, or a static SVG |
| Full-page chat widget on every page | Help page link; chat widget only on /pages/contact |
Step 5 — INP (Interaction to Next Paint)
INP replaced FID in 2024 and tends to be the silent killer on Shopify themes built before 2023. INP measures the longest input delay during a session — usually a tap that triggers JS-heavy code (add-to-cart, variant selector, mega-menu).
Common INP fixes
- Cart drawer: if it does a synchronous DOM rebuild on add-to-cart, switch to an HTML-fragment fetch (Section Rendering API) and morph the DOM only inside the drawer container.
- Variant selector: if changing a variant re-runs the entire product-form JS, scope updates to just the price + button + image. The reference is Shopify’s Dawn theme which does this well.
- Mega-menu: mega-menus that render all children of all top-level items on hover are an INP nightmare. Render children once on first hover, then cache.
Profile, don’t guess
Chrome DevTools → Performance → Record while clicking Add-to-Cart on a slow mobile profile. The long task that appears is your INP problem. Almost always one of: app script, cart-rebuild JS, or analytics event handler.
Step 6 — CLS (Cumulative Layout Shift)
CLS is almost always one of three things on Shopify:
- Images without width/height attributes. Add them. Always.
- Web fonts swapping in larger than fallback. Match the fallback font’s
size-adjust(CSS Font Metric Override). - Promo banners inserted via JS above the fold. Reserve the space in CSS first, fill in via JS second.
The fix is usually two CSS rules and a width/height on every <img>. CLS is the cheapest CWV win on the list.
Step 7 — Caching headers
Shopify handles most caching for you, but watch for:
- Asset URLs must be served with the
?v=…query Shopify adds. Some app blocks bypass this. InspectCache-Controlon theme assets in DevTools → Network. - CDN-hosted apps may serve no-cache headers by default. Drop the app, or wrap it in your own static asset if licensing allows.
Step 8 — Re-measure
Re-run PageSpeed Insights against the same URLs you measured at Step 0. Capture the new numbers. The lab improvement is usually visible immediately; field data (CrUX) takes about 28 days to reflect changes.
If the lab improvement is small or zero after a serious round of changes, that is a sign that the dominant cost lives somewhere you have not yet looked — usually a third-party script you missed, or a server-side delay (TTFB) hiding behind Cloudflare cache. Profile with Lighthouse “View Treemap” to see the JS breakdown by source.
Realistic expectations
A Shopify theme with no engagement on performance typically scores 30–50 on mobile PageSpeed Insights. After one focused session of the steps above, you should be at 70–85. Getting from 85 to 95+ usually requires harder trade-offs: dropping an app the marketing team likes, or rebuilding a section in vanilla JS. That is the conversation worth having.
A 1-second LCP improvement, in the real-user data Google ranks on, is typically worth 2–5% on organic conversion in my experience. The math justifies the engineering time at any meaningful AOV.
Need a paid audit on a specific Shopify store? I work with merchants across Slovenia and the EU on this exact scope. Get in touch.