Shopify Performance Optimization
Before writing code
Fetch live docs:
- Web-search
site:shopify.dev theme performance for theme optimization
- Web-search
site:shopify.dev hydrogen caching for Hydrogen caching strategies
- Web-search
site:web.dev core web vitals for current CWV guidelines and thresholds
- Web-search
site:shopify.dev image optimization cdn for image URL transforms
- Web-search
site:shopify.dev theme speed report for Shopify's built-in speed metrics
Liquid Rendering Performance
Template Optimization
- Minimize Liquid logic — complex loops and conditionals slow server-side rendering
- Use
{% render %} (not {% include %}) — isolated scope prevents variable conflicts
- Avoid nested loops —
O(n²) in Liquid is expensive
- Limit
forloop iterations with limit: parameter
- Pre-compute values with
{% assign %} instead of repeating expressions
Object Access
- Access specific properties:
{{ product.title }} not {{ product | json }}
- Avoid
all_products[handle] in loops — each is a separate data lookup
- Use section settings to pass data instead of global lookups
- Minimize use of
{{ content_for_header }} scripts (managed by Shopify — cannot remove, but minimize additional scripts)
Liquid Anti-Patterns
| Anti-Pattern |
Why It's Slow |
Better Approach |
Nested for loops |
O(n²) rendering |
Flatten data, use single loop |
all_products[handle] in loop |
Data fetch per iteration |
Pass products via section settings |
{% include %} with variables |
Shared scope causes conflicts |
Use {% render %} (isolated) |
Complex {% if %} chains |
Evaluated every render |
Simplify conditions, use {% case %} |
| Unused sections in templates |
Rendered even if hidden |
Remove from JSON template |
Asset Optimization
CSS
- Minimize CSS — remove unused styles
- Use
{{ 'style.css' | asset_url | stylesheet_tag }} for proper caching
- Critical CSS: inline above-the-fold styles in
<head>
- Defer non-critical CSS:
media="print">
JavaScript
- Defer non-critical JS:
<script defer> or dynamic import()
- Minimize JS bundles — Shopify themes don't need frameworks for most UI
- Use native browser APIs over jQuery
- Load third-party scripts asynchronously (
async attribute)
- Avoid render-blocking scripts in
<head>
Images
Shopify CDN image optimization:
# Responsive images with srcset
{{ image | image_url: width: 800 }}
{{ image | image_url: width: 400 }}
# Srcset pattern
<img
srcset="{{ image | image_url: width: 400 }} 400w,
{{ image | image_url: width: 800 }} 800w,
{{ image | image_url: width: 1200 }} 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
src="{{ image | image_url: width: 800 }}"
alt="{{ image.alt }}"
loading="lazy"
width="{{ image.width }}"
height="{{ image.height }}"
>
Key image practices:
- Use
loading="lazy" for below-the-fold images
- Use
fetchpriority="high" for LCP image
- Set explicit
width and height to prevent layout shift
- Shopify CDN automatically serves WebP/AVIF when supported
- URL parameters:
?width=, ?height=, ?crop=, ?format=
Fetch live docs: Web-search site:shopify.dev image_url filter parameters for current CDN transform options — new parameters are added over time.
Core Web Vitals
LCP (Largest Contentful Paint)
Target: < 2.5 seconds
- Preload hero/LCP image:
<link rel="preload" as="image" href="{{ image | image_url: width: 1200 }}">
- Use
fetchpriority="high" on LCP image
- Avoid lazy-loading above-the-fold images
- Minimize render-blocking CSS and JS
- Use server-side rendering (Liquid or Hydrogen SSR)
CLS (Cumulative Layout Shift)
Target: < 0.1
- Set explicit
width and height on all images and media
- Reserve space for dynamic content (ads, embeds, lazy-loaded images)
- Avoid inserting content above existing content after page load
- Use
aspect-ratio CSS property for responsive media containers
- Avoid dynamically injected banners or pop-ups that shift content
INP (Interaction to Next Paint)
Target: < 200ms
- Minimize main-thread blocking JavaScript
- Break up long tasks with
requestIdleCallback or setTimeout(fn, 0)
- Use CSS for animations and transitions (not JS)
- Debounce event handlers (scroll, resize, input)
- Avoid synchronous layout thrashing (read-then-write DOM patterns)
Fetch live docs: CWV thresholds and measurement methodology evolve. Web-search site:web.dev core web vitals thresholds for current targets.
Hydrogen Caching
Cache Strategies
// Pattern: apply cache strategy to storefront query
const data = await storefront.query(QUERY, {
cache: CacheLong(), // products, collections
});
const cart = await storefront.query(CART_QUERY, {
cache: CacheShort(), // dynamic data
});
| Strategy |
Use For |
CacheLong() |
Products, collections, pages |
CacheShort() |
Cart, personalized content |
CacheNone() |
Customer-specific data |
CacheCustom({...}) |
Fine-tuned scenarios |
Fetch live docs for exact TTL values — Hydrogen caching defaults may change across versions.
Streaming SSR
- Use
defer() in loaders for non-critical data
- Critical data renders immediately, deferred data streams in
- Show loading states with
<Suspense> + <Await>
- Preload routes with
<Link prefetch="intent">
Shopify CDN
Shopify's global CDN:
- Automatic for all theme assets and images
- Cache-Control headers managed by Shopify
- Image transformations via URL parameters
- No manual CDN configuration needed for themes
- Asset fingerprinting for cache busting
Measurement Tools
| Tool |
What It Measures |
| Shopify Theme Speed Report |
Overall theme score in admin |
| Google Lighthouse |
CWV + performance audit |
| WebPageTest |
Real-world loading waterfall |
| Chrome DevTools Performance |
JS profiling, layout shifts |
| Search Console CWV Report |
Field data from real users |
Best Practices
- Measure before optimizing — use Lighthouse, WebPageTest, Shopify's theme speed report
- Focus on LCP image optimization first (biggest impact for most stores)
- Lazy-load everything below the fold
- Minimize third-party scripts (analytics, chat widgets, social embeds)
- Use Shopify's built-in analytics over custom tracking scripts where possible
- For Hydrogen: cache aggressively, stream non-critical data, preload routes
- Test on real devices and slow connections (3G throttling)
- Set explicit dimensions on all media to prevent CLS
- Use
font-display: swap for custom fonts
Fetch the Shopify performance documentation, Core Web Vitals guides, and Hydrogen caching docs for exact optimization techniques and current best practices before implementing.
1---2name: shopify-performance3description: Optimize Shopify performance — Liquid rendering, asset optimization, CDN strategies, Core Web Vitals, Hydrogen caching, image optimization, preloading, and lazy loading. Use when improving Shopify store speed.4---56# Shopify Performance Optimization78## Before writing code910**Fetch live docs**:111. Web-search `site:shopify.dev theme performance` for theme optimization122. Web-search `site:shopify.dev hydrogen caching` for Hydrogen caching strategies133. Web-search `site:web.dev core web vitals` for current CWV guidelines and thresholds144. Web-search `site:shopify.dev image optimization cdn` for image URL transforms155. Web-search `site:shopify.dev theme speed report` for Shopify's built-in speed metrics1617## Liquid Rendering Performance1819### Template Optimization2021- Minimize Liquid logic — complex loops and conditionals slow server-side rendering22- Use `{% render %}` (not `{% include %}`) — isolated scope prevents variable conflicts23- Avoid nested loops — `O(n²)` in Liquid is expensive24- Limit `forloop` iterations with `limit:` parameter25- Pre-compute values with `{% assign %}` instead of repeating expressions2627### Object Access2829- Access specific properties: `{{ product.title }}` not `{{ product | json }}`30- Avoid `all_products[handle]` in loops — each is a separate data lookup31- Use section settings to pass data instead of global lookups32- Minimize use of `{{ content_for_header }}` scripts (managed by Shopify — cannot remove, but minimize additional scripts)3334### Liquid Anti-Patterns3536| Anti-Pattern | Why It's Slow | Better Approach |37|-------------|--------------|-----------------|38| Nested `for` loops | O(n²) rendering | Flatten data, use single loop |39| `all_products[handle]` in loop | Data fetch per iteration | Pass products via section settings |40| `{% include %}` with variables | Shared scope causes conflicts | Use `{% render %}` (isolated) |41| Complex `{% if %}` chains | Evaluated every render | Simplify conditions, use `{% case %}` |42| Unused sections in templates | Rendered even if hidden | Remove from JSON template |4344## Asset Optimization4546### CSS4748- Minimize CSS — remove unused styles49- Use `{{ 'style.css' | asset_url | stylesheet_tag }}` for proper caching50- Critical CSS: inline above-the-fold styles in `<head>`51- Defer non-critical CSS: `media="print" onload="this.media='all'"`5253### JavaScript5455- Defer non-critical JS: `<script defer>` or dynamic `import()`56- Minimize JS bundles — Shopify themes don't need frameworks for most UI57- Use native browser APIs over jQuery58- Load third-party scripts asynchronously (`async` attribute)59- Avoid render-blocking scripts in `<head>`6061### Images6263Shopify CDN image optimization:6465```liquid66# Responsive images with srcset67{{ image | image_url: width: 800 }}68{{ image | image_url: width: 400 }}6970# Srcset pattern71<img72 srcset="{{ image | image_url: width: 400 }} 400w,73 {{ image | image_url: width: 800 }} 800w,74 {{ image | image_url: width: 1200 }} 1200w"75 sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"76 src="{{ image | image_url: width: 800 }}"77 alt="{{ image.alt }}"78 loading="lazy"79 width="{{ image.width }}"80 height="{{ image.height }}"81>82```8384Key image practices:85- Use `loading="lazy"` for below-the-fold images86- Use `fetchpriority="high"` for LCP image87- Set explicit `width` and `height` to prevent layout shift88- Shopify CDN automatically serves WebP/AVIF when supported89- URL parameters: `?width=`, `?height=`, `?crop=`, `?format=`9091> **Fetch live docs**: Web-search `site:shopify.dev image_url filter parameters` for current CDN transform options — new parameters are added over time.9293## Core Web Vitals9495### LCP (Largest Contentful Paint)9697Target: < 2.5 seconds9899- Preload hero/LCP image: `<link rel="preload" as="image" href="{{ image | image_url: width: 1200 }}">`100- Use `fetchpriority="high"` on LCP image101- Avoid lazy-loading above-the-fold images102- Minimize render-blocking CSS and JS103- Use server-side rendering (Liquid or Hydrogen SSR)104105### CLS (Cumulative Layout Shift)106107Target: < 0.1108109- Set explicit `width` and `height` on all images and media110- Reserve space for dynamic content (ads, embeds, lazy-loaded images)111- Avoid inserting content above existing content after page load112- Use `aspect-ratio` CSS property for responsive media containers113- Avoid dynamically injected banners or pop-ups that shift content114115### INP (Interaction to Next Paint)116117Target: < 200ms118119- Minimize main-thread blocking JavaScript120- Break up long tasks with `requestIdleCallback` or `setTimeout(fn, 0)`121- Use CSS for animations and transitions (not JS)122- Debounce event handlers (scroll, resize, input)123- Avoid synchronous layout thrashing (read-then-write DOM patterns)124125> **Fetch live docs**: CWV thresholds and measurement methodology evolve. Web-search `site:web.dev core web vitals thresholds` for current targets.126127## Hydrogen Caching128129### Cache Strategies130131```typescript132// Pattern: apply cache strategy to storefront query133const data = await storefront.query(QUERY, {134 cache: CacheLong(), // products, collections135});136137const cart = await storefront.query(CART_QUERY, {138 cache: CacheShort(), // dynamic data139});140```141142| Strategy | Use For |143|----------|---------|144| `CacheLong()` | Products, collections, pages |145| `CacheShort()` | Cart, personalized content |146| `CacheNone()` | Customer-specific data |147| `CacheCustom({...})` | Fine-tuned scenarios |148149> **Fetch live docs** for exact TTL values — Hydrogen caching defaults may change across versions.150151### Streaming SSR152153- Use `defer()` in loaders for non-critical data154- Critical data renders immediately, deferred data streams in155- Show loading states with `<Suspense>` + `<Await>`156- Preload routes with `<Link prefetch="intent">`157158## Shopify CDN159160Shopify's global CDN:161- Automatic for all theme assets and images162- Cache-Control headers managed by Shopify163- Image transformations via URL parameters164- No manual CDN configuration needed for themes165- Asset fingerprinting for cache busting166167## Measurement Tools168169| Tool | What It Measures |170|------|-----------------|171| Shopify Theme Speed Report | Overall theme score in admin |172| Google Lighthouse | CWV + performance audit |173| WebPageTest | Real-world loading waterfall |174| Chrome DevTools Performance | JS profiling, layout shifts |175| Search Console CWV Report | Field data from real users |176177## Best Practices178179- Measure before optimizing — use Lighthouse, WebPageTest, Shopify's theme speed report180- Focus on LCP image optimization first (biggest impact for most stores)181- Lazy-load everything below the fold182- Minimize third-party scripts (analytics, chat widgets, social embeds)183- Use Shopify's built-in analytics over custom tracking scripts where possible184- For Hydrogen: cache aggressively, stream non-critical data, preload routes185- Test on real devices and slow connections (3G throttling)186- Set explicit dimensions on all media to prevent CLS187- Use `font-display: swap` for custom fonts188189Fetch the Shopify performance documentation, Core Web Vitals guides, and Hydrogen caching docs for exact optimization techniques and current best practices before implementing.