ezomfy
All posts
June 17, 202611 min read

How to Hit Lighthouse 95 on Shopify Without Rebuilding Your Theme

Most Shopify stores can achieve 95+ mobile Lighthouse scores by implementing 7 key optimizations on their existing theme, avoiding expensive rebuilds.

A

Ashraful

Shopify Select Partner

Two colorful lighthouses with a clear blue sky and fluffy clouds, showcasing maritime navigation. — Photo by Jan van der Wolf on Pexels

Too many Shopify merchants get the same advice: "Your theme is slow, you need a rebuild." I've seen stores pay tens of thousands for this, only to gain a few points on their Lighthouse score. That's usually a waste of money. The truth is, most Shopify stores can hit a 95+ mobile Lighthouse score by fixing a handful of critical issues on their existing theme. You don't need a complete overhaul; you need targeted, impactful optimizations. Before you even think about a rebuild, tackle these seven areas.

Stop Paying for Rebuilds: Your Shopify Theme Isn't the Problem (Yet)

The common narrative is that default themes or older themes are inherently slow. While some themes are better optimized out of the box, the core architecture of your Shopify theme is rarely the sole reason for a low Lighthouse score. I've audited over 500 Shopify stores, and almost every single time, the biggest culprits are easily fixable elements: inefficient image loading, bloated third-party scripts, and poor font handling. Chasing a "perfect" theme without addressing these low-hanging fruit is like trying to drain a bathtub with the tap still running. My approach to achieving Shopify Lighthouse 95 mobile focuses on practical, real-world changes that deliver tangible results without breaking the bank. If you're struggling with speed, start with a targeted approach to identify bottlenecks. This is exactly what we focus on in our Shopify speed optimization services.

The Hero Section: Preloading Your LCP Image

The Largest Contentful Paint (LCP) is often the most significant factor dragging down your Lighthouse score. It measures when the largest image or text block in the viewport becomes visible. For most Shopify stores, this is the main hero banner image. If your browser has to download other assets (CSS, JS) before it even knows it needs to fetch your hero image, your LCP will suffer. The fix is simple: tell the browser to fetch that critical image immediately.

Here's how you do it by adding a <link rel="preload"> tag in your <head> section. You'll need to find the specific image URL for your hero image. This often involves inspecting your theme's header.liquid or sections/hero.liquid files.

<head>
  ...
  {% comment %} Preload the LCP image {% endcomment %}
  {% assign hero_image = section.settings.image %} {% comment %} Adjust this based on your theme's image variable {% endcomment %}
  {% if hero_image != blank %}
    <link rel="preload" as="image" href="{{ hero_image | image_url: width: 1500 }}" fetchpriority="high">
  {% endif %}
  ...
</head>

Explanation:

  • as="image": Tells the browser it's an image.
  • href="...": The actual URL of your hero image. Use Shopify's image_url filter to get a responsive version. Adjust the width to match your hero image's typical display size.
  • fetchpriority="high": A hint to the browser that this resource is critical.

Implement this, and watch your LCP score jump. I've seen this alone improve scores by 10-20 points on mobile.

Deferring Third-Party JavaScript: Kill the Render Blocking

This is where most Shopify stores bleed performance. Every app you install, every tracking script, every marketing pixel adds JavaScript. And a lot of this JS is render-blocking. That means your browser pauses rendering the page until it's downloaded and executed those scripts. This directly impacts your First Contentful Paint (FCP) and LCP. The solution? Defer or asynchronously load non-critical JavaScript. Shopify's script tags don't always come with defer or async by default, especially for older or poorly built apps. You need to identify these scripts and modify how they're loaded.

How to Identify Render-Blocking Scripts

Run a Lighthouse audit. Look under "Eliminate render-blocking resources." You'll see a list of URLs. Pay close attention to scripts loaded from CDNs (cdn.shopify.com/s/..., static.klaviyo.com, etc.) or app-specific domains.

Implementing Deferral (Carefully)

The safest way to defer scripts is to add the defer attribute to their <script> tags. defer scripts execute in order, after the HTML is parsed. async scripts execute as soon as they're downloaded, potentially out of order, which can break dependencies. For most third-party apps, defer is the better choice. You'll typically find these script tags in theme.liquid or injected by apps into various sections.

Here’s a common pattern to move scripts to the end of the <body> and add defer if they don't have it:

// In theme.liquid, just before the closing </body> tag
<script>
  document.addEventListener('DOMContentLoaded', function() {
    var scriptsToDefer = [
      // List of script src URLs or patterns to defer
      '//cdn.shopify.com/s/files/1/0xxxx/assets/some-app-script.js',
      '//static.klaviyo.com/onsite/js/klaviyo.js',
      '/apps/rewards/app.js' // Example for an app proxy script
    ];

    scriptsToDefer.forEach(function(src) {
      var scriptElements = document.querySelectorAll('script[src*="' + src + '"]');
      scriptElements.forEach(function(script) {
        if (!script.hasAttribute('defer')) {
          script.setAttribute('defer', 'defer');
          // Optionally, move it to the end of the body
          // document.body.appendChild(script);
        }
      });
    });
  });
</script>

Important: Test rigorously after making these changes. Deferring certain scripts (like those for critical UI elements or analytics that must fire immediately) can break functionality. What most agencies get wrong is blindly deferring everything. You need to understand what each script does. I've seen this 50+ times on client audits: a store installs 15-20 apps over a few years, and suddenly their load time is abysmal. Each app adds its own JS, often without optimization. We recently worked with a fashion store that had a Lighthouse score of 45. They had 18 active apps, but 5 of them hadn't been used in months. Simply uninstalling those apps and manually cleaning up their lingering script tags, combined with deferring the remaining non-critical JS, boosted their mobile score to 88 within a week. This was without touching a single line of their theme's core structure. It wasn't about a rebuild; it was about surgical cleanup. If you're unsure where to start, an in-depth Shopify audit can pinpoint these issues precisely.

Font Loading: The font-display Swap

Web fonts are beautiful, but they can significantly impact your Core Web Vitals, especially Cumulative Layout Shift (CLS) and LCP. When custom fonts load late, browsers often hide text or show a fallback font, then "swap" to the custom font once it's ready. This causes a sudden reflow of content, leading to a jarring user experience and a high CLS score. The solution is font-display: swap;. This CSS property tells the browser: "If the custom font isn't ready, display the text immediately using a fallback font. Once the custom font loads, swap it in." This eliminates the "invisible text" (FOIT - Flash of Invisible Text) problem and reduces layout shifts.

Implementing font-display: swap;

You need to add font-display: swap; to every @font-face rule in your theme's CSS. These rules are usually in files like base.css, theme.css, or assets/theme.scss.liquid.

/* Example @font-face rule in your theme's CSS */
@font-face {
  font-family: 'YourCustomFont';
  src: url('your-custom-font.woff2') format('woff2');
  font-weight: normal;
  font-style: normal;
  font-display: swap; /* Add this line */
}

/* For Google Fonts, you might need to append &display=swap to the URL */
/* e.g., <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet"> */

After adding font-display: swap;, you'll notice text rendering much faster, even if it's initially in a fallback font. This is a huge win for perceived performance and CLS.

Intelligent Image Loading: Lazy Load Offscreen & Modern Formats

Images are often the heaviest assets on a Shopify store. Improper image handling is a primary cause of slow load times and poor LCP. There are two main strategies here: lazy loading and modern image formats.

Lazy Loading Offscreen Images

Lazy loading means images that are not immediately visible in the user's viewport (i.e., "offscreen") are only loaded when the user scrolls near them. This saves bandwidth and reduces initial page load time. Modern browsers support native lazy loading with the loading="lazy" attribute. Shopify themes generally include this for product images and other non-hero images by default. However, always double-check.

You can often find and modify image rendering in files like sections/main-product.liquid, snippets/product-card.liquid, or snippets/image-with-text.liquid.

<img
  src="{{ image | image_url: width: 400 }}"
  alt="{{ image.alt }}"
  loading="lazy" {% comment %} Ensure this attribute is present {% endcomment %}
  width="{{ image.width }}" {% comment %} Crucial for CLS {% endcomment %}
  height="{{ image.height }}" {% comment %} Crucial for CLS {% endcomment %}
>

Crucial Caveat: Do not lazy load your LCP image (usually the hero banner). That's counterproductive, as you want it to load as fast as possible. Preload it as discussed in H2 2.

Modern Image Formats (WebP, AVIF)

Shopify's CDN automatically converts and serves images in modern formats like WebP or AVIF to supported browsers. This is a massive win, as these formats offer superior compression without significant quality loss compared to JPEG or PNG. However, it only works if you're using Shopify's img_url filter correctly. Avoid uploading images directly to your theme assets and then referencing them, as these won't be optimized by Shopify's CDN. Always upload through the Shopify admin and use the Liquid image_url filter.

To verify, right-click an image on your live store and inspect it (or check the Network tab in DevTools). You should see Content-Type: image/webp or image/avif for modern browsers. If you're seeing image/jpeg or image/png, investigate how that specific image is being rendered.

Minimizing Layout Shift (CLS): Dimensions and Aspect Ratios

Cumulative Layout Shift (CLS) measures how much unexpected layout shift occurs during the loading of a page. It's incredibly frustrating for users when content jumps around while they're trying to read or click something. The primary culprit? Images, embeds, and ads that load without reserved space. When an image loads, if the browser doesn't know its dimensions, it allocates no space. Then, once the image data arrives, the browser suddenly pushes everything else down to make room. This is layout shift.

The Simple Fix: Explicit width and height Attributes

For every image in your theme, ensure it has width and height attributes. Shopify's image_tag Liquid filter usually handles this, but custom image implementations or third-party app images might miss it.

<img
  src="{{ product.featured_image | image_url: width: 800 }}"
  alt="{{ product.featured_image.alt }}"
  width="{{ product.featured_image.width }}"
  height="{{ product.featured_image.height }}"
>

This tells the browser exactly how much space to reserve before the image even loads. The browser can then render the page without content jumping.

Aspect Ratio Boxes for Responsive Images

For responsive images, where the actual display size changes, using width and height directly can be tricky without CSS to manage it. A robust technique is to use CSS to create an "aspect ratio box." This involves wrapping your image in a container and using padding-bottom (based on the image's aspect ratio) to reserve space.

Example CSS:

/* For an image with a 16:9 aspect ratio (height = 9/16 * width) */
.aspect-ratio-box {
  position: relative;
  width: 100%;
  padding-bottom: 56.25%; /* 9 / 16 = 0.5625 */
  overflow: hidden;
}

.aspect-ratio-box img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover; /* or contain, depending on desired behavior */
}

You would then apply .aspect-ratio-box to a div wrapping your img tag. This ensures space is reserved for the image, regardless of its final rendered size, preventing CLS. This technique, combined with explicit width and height attributes, virtually eliminates image-related CLS.

Kill Unused Apps and Unnecessary Code

This might sound obvious, but it's astonishing how many stores accumulate apps they no longer use. Every app, even "inactive" ones, can leave behind JavaScript, CSS, or Liquid snippets that still load, slowing down your store.

The Audit Process

  1. List all installed apps: Go to your Shopify admin -> Apps.
  2. Identify unused apps: Which ones haven't you touched in months? Which ones are redundant?
  3. Uninstall: Use Shopify's uninstall process. This often removes most of the code.
  4. Manual Cleanup: This is the critical step often missed. Even after uninstalling, apps can leave behind code snippets in theme.liquid, assets files, or snippets.
    • Search your theme code (using the theme editor or a local development environment) for app-specific keywords, script URLs, or Liquid tags related to uninstalled apps.
    • Backup your theme before doing any manual code removal.
    • Common places to check: theme.liquid (for script includes), assets (for app-specific CSS/JS files), sections and snippets (for Liquid renders).
    • If you're unsure, comment out the code first and test thoroughly before deleting.

This isn't just about apps. I've seen stores with old tracking pixels from campaigns long-ended, or A/B testing scripts for tests that finished months ago. Regularly review any custom code or snippets. Every line of unnecessary code is dead weight. If you're overwhelmed by this, our Shopify speed calculator can give you an initial estimate of your current performance, but a deep dive is always needed for cleanup.

Achieving a 95+ mobile Lighthouse score on Shopify is absolutely attainable for most stores without resorting to a costly theme rebuild. By systematically tackling these seven areas – hero image preloading, deferring third-party JS, using font-display: swap, intelligent lazy loading, modern image formats, fixing CLS, and ruthless app cleanup – you can dramatically improve your store's performance. These aren't just theoretical fixes; they are practical, proven strategies I've applied across hundreds of client stores. If you've gone through these steps and are still struggling, or you simply need expert guidance to implement them correctly, I offer a free 30-minute consultation call to discuss your specific store's needs and how we can help. Book your call at /consultation.

A

About the author

Ashraful

Shopify Select Partner, Top Rated Plus on Upwork. 700+ Shopify projects shipped over 7+ years — themes, apps, migrations, speed, Hydrogen. Solo shop, no agency middlemen.

Read the full story

Working on a Shopify project?

That's what I do every day. Pick whichever feels lower-friction.