Performance

How to Fix Shopify Core Web Vitals in 2025

LCP, CLS, and INP - what triggers each metric on a Shopify store, and the specific changes that consistently move the scores. A developer's field guide, not a Lighthouse score chase.

Core Web Vitals have been a Google ranking signal since mid-2021. For Shopify merchants, they matter twice: as an SEO factor and as a direct conversion driver - every 100ms of added load time reduces conversions by roughly 1%. A store scoring 45 on Performance isn't just ranking lower; it's losing revenue on every page load.

This guide covers the three current Core Web Vitals - LCP, CLS, and INP - with a focus on the specific Shopify patterns that cause each one. Not generic web performance advice: Shopify-specific causes and Shopify-specific fixes.

1. LCP (Largest Contentful Paint): the most impactful metric

LCP measures how long it takes for the largest visible element on the page to render - usually your hero image or banner. On Shopify, LCP scores are most commonly dragged down by three things:

Hero image not preloaded

The most common LCP killer on Shopify is a hero image that isn't preloaded. By default, Shopify themes lazy-load images - which is right for below-fold images but disastrous for the hero. Fix:

{% if section.index == 1 %}
  <link rel="preload" as="image"
    href="{{ section.settings.image | image_url: width: 1200 }}"
    fetchpriority="high">
{% endif %}

Add this inside your hero section's {% schema %} block at the top of theme.liquid, conditionally on the first section. This tells the browser to fetch the hero image immediately rather than waiting until the CSS and JS have parsed.

Hero image served at wrong size

Shopify's image CDN is excellent but you have to use it correctly. A hero image displayed at 1200px wide should be requested at 1200px, not the raw upload size (often 3000px+). In Liquid, always set a width parameter:

{{ section.settings.image | image_url: width: 1200 | image_tag:
  loading: 'eager',
  fetchpriority: 'high',
  widths: '750, 1100, 1500',
  sizes: '100vw' }}

The widths parameter generates a srcset so the browser picks the appropriate size for the viewport. On mobile (375px wide), it serves the 750px image rather than 1500px - half the bytes, significant LCP improvement.

Render-blocking third-party scripts

Google Tag Manager, Klaviyo, review widgets, live chat - these are all loaded in theme.liquid before the hero renders. Move them to load with defer or async. Better: audit which are actually necessary. Apps you trialled and disabled still often leave their scripts behind in theme.liquid. Every non-critical script in the <head> that loads synchronously delays LCP by the time it takes to download and execute.

Field data matters more than lab data. Google uses Chrome User Experience Report (CrUX) data - real user measurements - for ranking, not your Lighthouse score. A Lighthouse 90 with poor field data still ranks worse than a Lighthouse 75 with good field data. Check your field data in Google Search Console under Core Web Vitals.

2. CLS (Cumulative Layout Shift): the frustrating one

CLS measures how much the page layout shifts while loading. A score above 0.1 is considered poor. On Shopify, the most common CLS sources are:

Images without explicit dimensions

When a browser encounters an image with no width/height attributes, it doesn't know how much space to reserve. The image downloads, renders, and pushes content down - classic CLS. Fix: always set dimensions or an aspect-ratio CSS property. In Shopify's image_tag filter, pass width and height:

{{ product.featured_image | image_url: width: 800 | image_tag:
  width: 800,
  height: product.featured_image.height | divided_by: product.featured_image.width | times: 800 }}

Or use CSS aspect-ratio on image containers: aspect-ratio: 4/3; overflow: hidden; on the wrapper div. The browser reserves the space before the image loads.

Fonts causing FOUT/FOIT

Font loading that causes text to reflow causes CLS. The fix depends on where you're loading fonts from:

  • Google Fonts: Use font-display: swap and preload the most important weights. Or better: self-host the font files (eliminates the DNS lookup round trip entirely).
  • Self-hosted: Preload the woff2 file with <link rel="preload" as="font" type="font/woff2" crossorigin> in your <head>.
  • font-display: block over swap if invisible text causes more layout shift than the font swap itself - test both.

Third-party widgets injected after load

Review badges, cookie banners, chat widgets, and promotional bars injected by apps often push content down after the page has already rendered. CLS from injected content is both common and preventable:

  • Give cookie banners a fixed position so they don't affect document flow
  • Pre-allocate height for review badge containers: min-height: 32px on the container div
  • Move promo bars to be server-rendered (in Liquid) rather than injected via JS

3. INP (Interaction to Next Paint): the new metric

INP replaced FID in March 2024. Where FID measured the delay to first interaction, INP measures the responsiveness of all interactions throughout the page lifetime. On Shopify, poor INP almost always traces to JavaScript blocking the main thread.

App scripts on the main thread

The majority of Shopify apps inject JavaScript that runs on load and continuously throughout the page lifecycle. Loyalty apps, review widgets, recommendation engines - every one of them competes for main thread time. When a user clicks Add to Cart and there are 15 apps fighting for the main thread, the response feels sluggish even if the Shopify AJAX call itself is fast.

To identify which apps are the problem, run Chrome DevTools Performance panel while interacting with the page. Look for Long Tasks (orange bars in the Main thread) - the script filename will tell you which app is responsible. Your options:

  • Defer non-critical app scripts to load after user interaction using an Intersection Observer or a setTimeout
  • Remove apps that add more JS weight than conversion value
  • Replace multiple single-purpose apps with one that covers multiple functions

Variant selection and cart operations

On product pages, variant selection should feel instant. If there's a noticeable delay between selecting a variant and the price/availability updating, JavaScript is doing too much synchronous work on the main thread. Common culprits: watching every DOM node for changes rather than using event delegation, re-rendering entire product sections on variant change rather than just the changed elements, and running heavy image gallery re-initialization on every variant switch.

The Shopify app audit

Before any code changes, run a thorough app audit. Load your store in an incognito window and open DevTools Network tab. Filter by JS. Count how many scripts are loading that aren't your theme's own code. On an average Shopify store I audit, the split is roughly 40% theme JS and 60% app JS.

A useful rule of thumb: if you can't immediately name what every third-party script does and confirm it's actively driving revenue, it's a candidate for removal. Dead apps from trials you abandoned years ago are common - and they're still loading their JavaScript on every page.

Quick-win checklist

  • Add rel="preload" for the hero image in your first section's Liquid template
  • Ensure hero images are served via Shopify CDN with correct widths srcset parameters
  • Audit theme.liquid for abandoned app scripts (look for commented-out or orphaned <script> tags)
  • Add explicit width/height to all images above the fold
  • Self-host your primary font and add a <link rel="preload"> for the woff2 file
  • Set font-display: swap on all @font-face declarations
  • Give cookie banners position: fixed so they don't shift document flow
  • Run Chrome DevTools Performance panel and identify Long Tasks during page interactions
  • Check field data in Google Search Console - Lighthouse lab score is a proxy, field data is what Google uses

Core Web Vitals work is iterative. You won't hit 90+ across all three metrics from a single round of changes - especially on an established store with accumulated app and theme complexity. The approach that works: fix the highest-impact issue, measure the field data shift over 28 days (CrUX data lags), then fix the next one. Tools like Google Search Console, PageSpeed Insights, and the Chrome UX Report are your ground truth throughout.

If you want a prioritised list of exactly what's dragging your specific store's scores down rather than working through this generically, that's what the Speed Audit delivers.

Filip Rastovic
Filip Rastovic
Shopify Developer & CRO Specialist · Stargazer Studio

Scoring below 90 on Performance?

A Speed Audit identifies exactly what's dragging your Shopify store's Core Web Vitals down and gives you a prioritised fix list - delivered in 24 hours.

Book a free call More articles
Filip Rastovic
Book a Call Get started today