Most Shopify stores struggle with poor mobile performance. Out of the box, even modern Shopify 2.0 themes can score under 40 on Google PageSpeed Insights (Mobile) once apps, tracking scripts, and oversized product media get added.
As a full-stack Shopify developer who has delivered 350+ commercial projects, slow load times are the #1 complaint I hear from e-commerce store owners. Every 1-second delay in page load can drop conversion rates by up to 7%.
In this article, I’ll break down the exact technical blueprint I used to take a client's production Shopify store from a 34 Mobile Performance Score to a 96+, without removing essential features or tracking apps.
1. The Starting Benchmark: Why Most Shopify Stores Fail
Before making changes, we ran an in-depth audit using Google PageSpeed Insights and WebPageTest:
-
Mobile Score:
34 / 100 -
First Contentful Paint (FCP):
2.9s -
Largest Contentful Paint (LCP):
5.8s(Failing Core Web Vitals) -
Total Blocking Time (TBT):
940ms -
Cumulative Layout Shift (CLS):
0.32
The culprits were classic e-commerce bottlenecks:
- Render-blocking third-party scripts loaded in
theme.liquid. - Hero banner images lacking responsive
srcsetandfetchpriority="high". - Multiple unoptimized CSS files and unused legacy plugins.
- Zombie app snippets left behind after apps were uninstalled.
2. Step 1: Purging "Zombie" Liquid Snippets & App Bloat
When merchants uninstall apps from the Shopify App Store, apps frequently leave behind orphaned Liquid includes, tracking pixels, and CSS files in the theme codebase.
In layout/theme.liquid, we audited all {% render %} and {% include %} tags:
{%- comment -%}
BEFORE: Unused legacy apps executing on every page
{% render 'old-review-widget-header' %}
{% render 'uninstalled-upsell-tracker' %}
{%- endcomment -%}
Removing dead snippets alone eliminated 18 external HTTP network requests and shaved 380 KB of uncompressed JavaScript from the initial document head.
3. Step 2: Optimizing LCP (Largest Contentful Paint)
The hero banner on the homepage was the primary LCP element. It was being lazy-loaded by default, delaying its render until long after JavaScript finished parsing!
The Rule for E-Commerce Images:
- Above-the-fold (Hero/Main Banner): Eager load, high fetch priority, no lazy loading.
-
Below-the-fold (Product Grids, Reviews, Footer): Lazy load with native
loading="lazy".
Here is the optimized Liquid pattern used for the hero image:
{%- if section.index == 1 -%}
{{ section.settings.image | image_url: width: 1500 | image_tag:
widths: '375, 550, 750, 1100, 1500',
sizes: '100vw',
loading: 'eager',
fetchpriority: 'high',
decoding: 'async',
class: 'hero-banner__image',
alt: section.settings.image.alt | escape
}}
{%- else -%}
{{ section.settings.image | image_url: width: 1200 | image_tag:
widths: '375, 550, 750, 1100',
sizes: '(min-width: 768px) 50vw, 100vw',
loading: 'lazy',
decoding: 'async',
class: 'content-image'
}}
{%- endif -%}
Result: LCP dropped from 5.8s down to 1.8s immediately.
4. Step 3: Eliminating Cumulative Layout Shift (CLS)
CLS kills user experience when elements jump around while assets download.
We fixed layout shifts with two key changes:
-
Explicit Aspect Ratios on Image Containers:
Always declare explicit
widthandheightattributes or use CSSaspect-ratio:
.product-card__media {
aspect-ratio: 1 / 1;
overflow: hidden;
background: #f4f4f4; /* placeholder color prevents visual jumping */
}
- Announcement Bar Height Reservation: Dynamic announcement bars or countdown timers often push the entire navbar down when they mount. Reserving a min-height via CSS prevents the layout from reflowing.
Result: CLS plummeted from 0.32 down to 0.01 (well within Google's "Good" green threshold).
5. Step 4: Deferring Third-Party Scripts with requestIdleCallback
Analytics tags, chat widgets (Gorgias, Zendesk), and review stars (Klaviyo, Yotpo) don't need to load during the critical 1.5-second rendering window.
Instead of inserting them as synchronous scripts in <head>, we delayed non-critical scripts until the browser's main thread becomes idle:
function loadNonCriticalScripts() {
const scripts = [
'https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=YOUR_ID',
// other third-party services
];
scripts.forEach((src) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
document.body.appendChild(script);
});
}
// Execute when browser is idle or after first interaction
if ('requestIdleCallback' in window) {
window.requestIdleCallback(loadNonCriticalScripts, { timeout: 3000 });
} else {
window.addEventListener('load', () => setTimeout(loadNonCriticalScripts, 2000));
}
This reduced Total Blocking Time (TBT) from 940ms down to 85ms, liberating the main thread for smooth scrolling and instant touch responsiveness.
6. The Final Results
After testing the optimizations on production:
| Metric | Before Optimization | After Optimization | Status |
|---|---|---|---|
| Mobile Performance | 34 / 100 |
96 / 100 |
🟢 Passed |
| Desktop Performance | 68 / 100 |
99 / 100 |
🟢 Passed |
| LCP (Largest Contentful Paint) | 5.8s |
1.6s |
🟢 Good |
| FCP (First Contentful Paint) | 2.9s |
0.9s |
🟢 Good |
| TBT (Total Blocking Time) | 940ms |
85ms |
🟢 Good |
| CLS (Cumulative Layout Shift) | 0.32 |
0.01 |
🟢 Good |
Key Takeaways for Developers
-
Audit your Liquid assets: Don't trust theme uninstalls; check
theme.liquidmanually for leftover app code. -
Master
fetchpriority="high": Always use eager loading and high priority for your primary hero image. - Control third-party execution: Never let non-essential tracking scripts block the primary rendering thread.
Need Help Optimizing Your Shopify Store?
I am a Shopify & Laravel Full-Stack Developer specializing in custom theme builds, Vue.js storefronts, and conversion-rate optimization.
- Explore my portfolio and past projects: hamzahameed.dev
- Need an audit or custom development? Book a Consultation on Cal.com
- Connect on GitHub
Top comments (0)