Every Shopify store owner has Googled "how to speed up Shopify." The results are usually a mix of generic advice ("compress your images!") and vendor-sponsored content pushing yet another app. This post is different.
Below are 7 specific optimizations we have implemented across client stores, with real before/after Core Web Vitals data. These are not theoretical. Every number comes from anonymized client projects, measured using CrUX (Chrome User Experience Report) field data -- not synthetic Lighthouse runs.
Average (After)
Critical Image Preloading and Lazy-Load Strategy
The single biggest LCP improvement we make on every store. Most Shopify themes lazy-load everything, including the hero image that IS the LCP element. This is backwards.
What we do
- Preload the LCP image using
<link rel="preload">in the<head> - Remove
loading="lazy"from above-the-fold images (hero, first product image) - Add
fetchpriority="high"to the LCP element - Use responsive
srcsetwith Shopify's image CDN transforms to serve correctly sized images - Lazy-load everything below the fold using native
loading="lazy"(no JavaScript library)
<!-- Before: Hero image lazy-loaded (bad) -->
<img src="{{ section.settings.hero_image | image_url: width: 1920 }}"
loading="lazy" alt="Hero">
<!-- After: Hero image preloaded, priority fetched -->
{%- liquid
assign hero_img = section.settings.hero_image
assign hero_url = hero_img | image_url: width: 1200
-%}
<link rel="preload" as="image" href="{{ hero_url }}"
imagesrcset="{{ hero_img | image_url: width: 600 }} 600w,
{{ hero_img | image_url: width: 900 }} 900w,
{{ hero_img | image_url: width: 1200 }} 1200w"
imagesizes="100vw">
<img src="{{ hero_url }}"
srcset="{{ hero_img | image_url: width: 600 }} 600w,
{{ hero_img | image_url: width: 900 }} 900w,
{{ hero_img | image_url: width: 1200 }} 1200w"
sizes="100vw"
fetchpriority="high"
alt="Hero">
App Bloat Audit and Script Removal
The average Shopify store we audit has 12-18 apps installed, but only uses 8-10 actively. The unused apps still inject JavaScript into every page load. This is the single most impactful INP optimization.
Our app audit process
- List all installed apps and their last-used date in the Shopify admin
- Run a script audit using Chrome DevTools Network tab -- identify every third-party script and which app owns it
- Measure individual script impact by blocking each script and measuring the performance delta
- Remove unused apps -- uninstall completely (not just deactivate)
- Verify leftover code -- many apps leave behind Liquid snippets, theme assets, and script tags even after uninstalling. Manually clean these out.
Pro Tip: After uninstalling apps, search your theme code for the app's handle. Common leftover patterns:
{% render 'app-name' %},{{ 'app-name.js' | asset_url | script_tag }}, and inline<script>blocks with the app's domain.
Third-Party Script Deferral with Partytown or requestIdleCallback
Even essential third-party scripts (analytics, chat widgets, review widgets) do not need to run during initial page load. Deferring them to after the main thread is idle dramatically improves INP and TBT (Total Blocking Time).
Scripts we defer
- Google Analytics / GA4 -- defer by 2 seconds or use
requestIdleCallback - Facebook Pixel -- load after
DOMContentLoaded - Chat widgets (Tidio, Gorgias, Zendesk) -- load on user interaction (scroll or click)
- Review widgets (Judge.me, Yotpo) -- load when the review section enters viewport via IntersectionObserver
- Recommendation carousels -- load on viewport intersection
// Defer non-critical scripts until browser is idle
function loadDeferredScripts() {
const scripts = [
'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX',
'https://connect.facebook.net/en_US/fbevents.js'
];
scripts.forEach(src => {
const s = document.createElement('script');
s.src = src;
s.async = true;
document.head.appendChild(s);
});
}
if ('requestIdleCallback' in window) {
requestIdleCallback(loadDeferredScripts);
} else {
setTimeout(loadDeferredScripts, 3000);
}
Shopify Image CDN Tuning and Format Optimization
Shopify's CDN automatically serves WebP and AVIF formats when the browser supports them. But most themes do not take full advantage of this. Here is what we optimize:
- Use
image_urlfilter (notimg_url) for responsive images with proper width parameters - Set explicit width and height attributes on all
<img>tags to prevent CLS - Use
format: 'pjpg'for product images (progressive JPEG loads perceived faster) - Cap maximum image width to the actual display size -- never serve a 4000px image for a 600px container
- Use CSS
aspect-ratioon image containers to reserve space before load
Liquid Render Optimization (Reducing Server Response Time)
Shopify's servers have to parse and execute your Liquid templates on every request. Complex Liquid code directly impacts TTFB. Here are the biggest Liquid performance offenders we fix:
- Nested for-loops in collection pages -- iterating over products, then variants, then metafields creates O(n^3) complexity. Use
assignto pre-filter. - Excessive
include/rendercalls -- each one adds parse overhead. Inline small snippets instead of rendering them. - Unused sections in JSON templates -- remove any sections in your template JSON that are disabled or hidden. They still render server-side.
- Complex
wherefilters on large collections -- use Shopify's Collection API filtering instead of Liquid loops. - Metafield overuse on listing pages -- each metafield access on a collection page with 50 products is 50 additional queries. Access metafields only on product pages.
{%- comment -%} BEFORE: Nested loops (slow) {%- endcomment -%}
{% for product in collection.products %}
{% for variant in product.variants %}
{% if variant.metafields.custom.badge != blank %}
<span>{{ variant.metafields.custom.badge }}</span>
{% endif %}
{% endfor %}
{% endfor %}
{%- comment -%} AFTER: Pre-filter and flatten (fast) {%- endcomment -%}
{% for product in collection.products %}
{%- assign first_badge = product.metafields.custom.badge -%}
{% if first_badge != blank %}
<span>{{ first_badge }}</span>
{% endif %}
{% endfor %}
CSS Critical Path Extraction and Font Loading Strategy
Render-blocking CSS is a major LCP bottleneck. Shopify's content_for_header injects significant render-blocking resources that you cannot remove. But you can control everything else.
What we implement
- Inline critical CSS for above-the-fold content (header, hero section) directly in the
<head> - Async load non-critical CSS using
<link rel="preload" as="style" onload="this.rel='stylesheet'"> - Font display: swap on all custom fonts to prevent invisible text during load
- Preconnect to font CDNs --
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> - Subset fonts -- only load the character sets you actually use (Latin, not Latin Extended + Cyrillic)
Layout Shift Prevention (CLS Below 0.05)
CLS is the most underrated Core Web Vital. It directly affects user experience -- elements jumping around the page as resources load makes a store feel broken. Common CLS offenders on Shopify:
- Images without dimensions -- the browser cannot reserve space until the image loads
- Dynamic content injection -- review widgets, "recently viewed" sections, and announcement bars that load after page render
- Web fonts causing FOUT (Flash of Unstyled Text) -- text reflows when the custom font loads
- Sticky headers with dynamic height -- header changes height on scroll, pushing content down
Our CLS prevention checklist
- Set explicit
widthandheighton every<img>and<video>element - Use CSS
aspect-ratioon containers that hold dynamic content - Reserve space for ad slots and widget injection points with
min-height - Use
font-display: optionalfor body text to prevent FOUT entirely - Set a fixed height for the header with
min-heightin CSS - Pre-render announcement bar content server-side (not via JavaScript)
The Business Impact
Improved Core Web Vitals are not just about a higher PageSpeed score. Across the 12 stores where we implemented all 7 optimizations, we measured:
rate increase
reduction
growth (90 days)
Key insight: The conversion rate improvements correlated most strongly with INP improvements, not LCP. Users who experience a responsive, interactive storefront are more likely to add to cart and complete checkout. Speed is not just about the initial load -- it is about how the page feels during interaction.
Where to Start
If you can only do three things from this list, do these: (1) fix your LCP image loading strategy, (2) audit and remove unused apps, and (3) defer all non-critical third-party scripts. These three changes alone typically move a store from a 40-60 PageSpeed score to 75-85.
For a comprehensive performance audit of your Shopify store, including CrUX data analysis and a prioritized optimization roadmap, learn about our Shopify audit service.