shopify-performance
Shopify Performance Optimization
Expert guidance for optimizing Shopify store performance including theme speed, asset optimization, and Core Web Vitals.
When to Use This Skill
Invoke this skill when:
- Optimizing Shopify theme performance and load times
- Improving Lighthouse or PageSpeed Insights scores
- Reducing Time to Interactive (TTI) or Largest Contentful Paint (LCP)
- Optimizing images for faster loading
- Implementing lazy loading for images and videos
- Minifying and optimizing JavaScript and CSS
- Reducing JavaScript bundle sizes
- Improving Core Web Vitals metrics (LCP, FID, CLS)
- Implementing caching strategies
- Optimizing Liquid template rendering
- Reducing server response times
- Improving mobile performance
Core Capabilities
1. Image Optimization
Images are typically the largest assets - optimize aggressively.
Use Shopify CDN Image Sizing:
{# ❌ Don't load full-size images #}
<img src="{{ product.featured_image.src }}" alt="{{ product.title }}">
{# ✅ Use img_url filter with appropriate size #}
<img
src="{{ product.featured_image | img_url: '800x800' }}"
alt="{{ product.featured_image.alt | escape }}"
loading="lazy"
width="800"
height="800"
>
Responsive Images:
<img
src="{{ image | img_url: '800x' }}"
srcset="
{{ image | img_url: '400x' }} 400w,
{{ image | img_url: '800x' }} 800w,
{{ image | img_url: '1200x' }} 1200w,
{{ image | img_url: '1600x' }} 1600w
"
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt="{{ image.alt | escape }}"
loading="lazy"
width="800"
height="800"
>
Modern Image Formats:
<picture>
{# WebP for modern browsers #}
<source
type="image/webp"
srcset="
{{ image | img_url: '400x', format: 'pjpg' }} 400w,
{{ image | img_url: '800x', format: 'pjpg' }} 800w
"
>
{# Fallback to JPEG #}
<img
src="{{ image | img_url: '800x' }}"
srcset="
{{ image | img_url: '400x' }} 400w,
{{ image | img_url: '800x' }} 800w
"
alt="{{ image.alt | escape }}"
loading="lazy"
>
</picture>
Lazy Loading:
{# Native lazy loading #}
<img
src="{{ image | img_url: '800x' }}"
alt="{{ image.alt | escape }}"
loading="lazy"
decoding="async"
>
{# Eager load above-the-fold images #}
{% if forloop.index <= 3 %}
<img src="{{ image | img_url: '800x' }}" loading="eager">
{% else %}
<img src="{{ image | img_url: '800x' }}" loading="lazy">
{% endif %}
Preload Critical Images:
{# In <head> for hero images #}
<link
rel="preload"
as="image"
href="{{ section.settings.hero_image | img_url: '1920x' }}"
imagesrcset="
{{ section.settings.hero_image | img_url: '800x' }} 800w,
{{ section.settings.hero_image | img_url: '1920x' }} 1920w
"
imagesizes="100vw"
>
2. JavaScript Optimization
Reduce JS payload and execution time.
Defer Non-Critical JavaScript:
{# ❌ Blocking JavaScript #}
<script src="{{ 'theme.js' | asset_url }}"></script>
{# ✅ Deferred JavaScript #}
<script src="{{ 'theme.js' | asset_url }}" defer></script>
{# ✅ Async for independent scripts #}
<script src="{{ 'analytics.js' | asset_url }}" async></script>
Inline Critical JavaScript:
{# Inline small, critical scripts #}
<script>
// Critical initialization code
document.documentElement.classList.remove('no-js');
document.documentElement.classList.add('js');
</script>
Code Splitting:
// Load features only when needed
async function loadCart() {
const { Cart } = await import('./cart.js');
return new Cart();
}
// Load on interaction
document.querySelector('.cart-icon').addEventListener('click', async () => {
const cart = await loadCart();
cart.open();
}, { once: true });
Remove Unused JavaScript:
// ❌ Don't load libraries you don't use
// Example: Don't include entire jQuery if you only need a few functions
// ✅ Use native alternatives
// Instead of: $('.selector').hide()
// Use: document.querySelector('.selector').style.display = 'none';
// Instead of: $.ajax()
// Use: fetch()
Minify JavaScript:
# Use build tools to minify
npm install terser --save-dev
# Minify
terser theme.js -o theme.min.js -c -m
3. CSS Optimization
Optimize stylesheets for faster rendering.
Critical CSS:
{# Inline critical above-the-fold CSS in <head> #}
<style>
/* Critical CSS only (header, hero) */
.header { /* ... */ }
.hero { /* ... */ }
.button { /* ... */ }
</style>
{# Load full CSS deferred #}
<link
rel="preload"
href="{{ 'theme.css' | asset_url }}"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
>
<noscript>
<link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
</noscript>
Remove Unused CSS:
# Use PurgeCSS to remove unused styles
npm install @fullhuman/postcss-purgecss --save-dev
# Configure in postcss.config.js
module.exports = {
plugins: [
require('@fullhuman/postcss-purgecss')({
content: ['./**/*.liquid'],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [],
}),
],
};
Minify CSS:
# Use cssnano
npm install cssnano --save-dev
# Minify
npx cssnano style.css style.min.css
Avoid @import:
/* ❌ Don't use @import (blocks rendering) */
@import url('fonts.css');
/* ✅ Use multiple <link> tags instead */
<link rel="stylesheet" href="{{ 'main.css' | asset_url }}">
<link rel="stylesheet" href="{{ 'fonts.css' | asset_url }}">
4. Font Optimization
Optimize web fonts for faster text rendering.
Font Loading:
{# Preload fonts #}
<link
rel="preload"
href="{{ 'font.woff2' | asset_url }}"
as="font"
type="font/woff2"
crossorigin
>
{# Font face with font-display #}
<style>
@font-face {
font-family: 'CustomFont';
src: url('{{ 'font.woff2' | asset_url }}') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap; /* Show fallback font immediately */
}
</style>
System Font Stack:
/* Use system fonts for instant rendering */
body {
font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
sans-serif;
}
Subset Fonts:
/* Load only required characters */
@font-face {
font-family: 'CustomFont';
src: url('font-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}
5. Liquid Template Optimization
Optimize Liquid rendering for faster server response.
Cache Expensive Operations:
{# ❌ Repeated calculations #}
{% for i in (1..10) %}
{{ collection.products.size }} {# Calculated 10 times #}
{% endfor %}
{# ✅ Cache result #}
{% assign product_count = collection.products.size %}
{% for i in (1..10) %}
{{ product_count }}
{% endfor %}
Use limit and offset:
{# ❌ Iterate full array and break #}
{% for product in collection.products %}
{% if forloop.index > 5 %}{% break %}{% endif %}
{{ product.title }}
{% endfor %}
{# ✅ Use limit #}
{% for product in collection.products limit: 5 %}
{{ product.title }}
{% endfor %}
Avoid Nested Loops:
{# ❌ O(n²) complexity #}
{% for product in collection.products %}
{% for variant in product.variants %}
{# Expensive nested loop #}
{% endfor %}
{% endfor %}
{# ✅ Flatten or preprocess #}
{% assign all_variants = collection.products | map: 'variants' | flatten %}
{% for variant in all_variants limit: 50 %}
{{ variant.title }}
{% endfor %}
Prefer render over include:
{# ❌ include (slower, shared scope) #}
{% include 'product-card' %}
{# ✅ render (faster, isolated scope) #}
{% render 'product-card', product: product %}
Use section-specific stylesheets:
{# Scope CSS to section for better caching #}
{% stylesheet %}
.my-section { /* ... */ }
{% endstylesheet %}
{# Scope JavaScript to section #}
{% javascript %}
class MySection { /* ... */ }
{% endjavascript %}
6. Third-Party Script Optimization
Minimize impact of external scripts.
Defer Third-Party Scripts:
{# ❌ Blocking third-party script #}
<script src="https://external.com/script.js"></script>
{# ✅ Async or defer #}
<script src="https://external.com/script.js" async></script>
{# ✅ Load on user interaction #}
<script>
let gaLoaded = false;
function loadGA() {
if (gaLoaded) return;
const script = document.createElement('script');
script.src = 'https://www.googletagmanager.com/gtag/js?id=GA_ID';
script.async = true;
document.head.appendChild(script);
gaLoaded = true;
}
// Load on scroll or after delay
window.addEventListener('scroll', loadGA, { once: true });
setTimeout(loadGA, 3000);
</script>
Use Facade Pattern:
{# Show placeholder instead of embedding heavy iframe #}
<div class="video-facade" data-video-id="abc123">
<img src="thumbnail.jpg" alt="Video">
<button onclick="loadVideo(this)">Play Video</button>
</div>
<script>
function loadVideo(btn) {
const facade = btn.parentElement;
const videoId = facade.dataset.videoId;
const iframe = document.createElement('iframe');
iframe.src = `https://www.youtube.com/embed/${videoId}?autoplay=1`;
facade.replaceWith(iframe);
}
</script>
7. Caching Strategies
Leverage browser and CDN caching.
Asset Versioning:
{# Shopify auto-versions assets #}
<link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
{# Outputs: /cdn/.../theme.css?v=12345678 #}
Long Cache Headers:
{# Shopify CDN sets appropriate cache headers #}
{# CSS/JS: 1 year #}
{# Images: 1 year #}
Service Worker (Advanced):
// sw.js - Cache static assets
self.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll([
'/cdn/.../theme.css',
'/cdn/.../theme.js',
'/cdn/.../logo.png',
]);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
8. Core Web Vitals Optimization
Improve Google's Core Web Vitals metrics.
Largest Contentful Paint (LCP):
{# Optimize largest element load time #}
{# 1. Preload hero image #}
<link rel="preload" as="image" href="{{ hero_image | img_url: '1920x' }}">
{# 2. Use priority hint #}
<img src="{{ hero_image | img_url: '1920x' }}" fetchpriority="high">
{# 3. Optimize server response time (use Shopify CDN) #}
{# 4. Remove render-blocking resources #}
<script src="theme.js" defer></script>
First Input Delay (FID) / Interaction to Next Paint (INP):
// 1. Reduce JavaScript execution time
// 2. Break up long tasks
function processItems(items) {
// ❌ Long task
items.forEach(item => processItem(item));
// ✅ Break into smaller chunks
async function processInChunks() {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
// Yield to main thread every 50 items
if (i % 50 === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
}
processInChunks();
}
// 3. Use requestIdleCallback
requestIdleCallback(() => {
// Non-critical work
});
Cumulative Layout Shift (CLS):
{# 1. Always set width and height on images #}
<img
src="{{ image | img_url: '800x' }}"
width="800"
height="600"
alt="Product"
>
{# 2. Reserve space for dynamic content #}
<div style="min-height: 400px;">
{# Content loads here #}
</div>
{# 3. Use aspect-ratio for responsive images #}
<style>
.image-container {
aspect-ratio: 16 / 9;
}
</style>
9. Performance Monitoring
Track performance metrics.
Measure Core Web Vitals:
// Load web-vitals library
import { getCLS, getFID, getLCP } from 'web-vitals';
function sendToAnalytics({ name, value, id }) {
// Send to analytics
gtag('event', name, {
event_category: 'Web Vitals',
event_label: id,
value: Math.round(name === 'CLS' ? value * 1000 : value),
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
Performance Observer:
// Monitor long tasks
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
console.warn('Long task detected:', entry.duration, 'ms');
}
});
observer.observe({ entryTypes: ['longtask'] });
Performance Checklist
Images:
- Use
img_urlfilter with appropriate sizes - Implement responsive images with
srcset - Add
loading="lazy"to below-fold images - Set explicit
widthandheightattributes - Preload critical hero images
- Use modern formats (WebP)
JavaScript:
- Defer or async all non-critical scripts
- Minify and bundle JavaScript
- Code-split large bundles
- Remove unused code
- Lazy load features on interaction
CSS:
- Inline critical CSS
- Defer non-critical CSS
- Remove unused styles
- Minify stylesheets
- Avoid
@import
Fonts:
- Preload critical fonts
- Use
font-display: swap - Consider system font stack
- Subset fonts when possible
Third-Party:
- Audit all third-party scripts
- Load scripts async or on interaction
- Use facade pattern for heavy embeds
- Monitor third-party impact
Liquid:
- Cache expensive calculations
- Use
limitinstead of manual breaks - Prefer
renderoverinclude - Avoid nested loops
Core Web Vitals:
- LCP < 2.5s
- FID < 100ms (INP < 200ms)
- CLS < 0.1
Best Practices
- Test on real devices - Mobile 3G performance matters
- Use Lighthouse for performance audits
- Monitor Core Web Vitals in production
- Optimize above-the-fold content first
- Lazy load everything else below the fold
- Minimize main thread work for better interactivity
- Use Shopify CDN for all assets
- Version assets for effective caching
- Compress images before uploading
- Regular performance audits to catch regressions
Integration with Other Skills
- shopify-liquid - Use when optimizing Liquid template code
- shopify-theme-dev - Use when organizing theme assets
- shopify-debugging - Use when troubleshooting performance issues
- shopify-api - Use when optimizing API request patterns
Quick Reference
{# Images #}
<img src="{{ image | img_url: '800x' }}" loading="lazy" width="800" height="800">
{# Scripts #}
<script src="{{ 'theme.js' | asset_url }}" defer></script>
{# Fonts #}
<link rel="preload" href="{{ 'font.woff2' | asset_url }}" as="font" crossorigin>
{# Critical CSS #}
<style>/* Critical CSS */</style>
<link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style" onload="this.rel='stylesheet'">
{# Responsive images #}
<img srcset="{{ image | img_url: '400x' }} 400w, {{ image | img_url: '800x' }} 800w">
More from henkisdabro/wookstar-claude-code-plugins
tampermonkey
Write and debug Tampermonkey userscripts for browser automation, page modification, and web enhancement. Use whenever the user mentions userscripts, Tampermonkey, Greasemonkey, Violentmonkey, or wants to write a script that runs on a website - even if they don't say 'userscript' explicitly. Also trigger for: injecting JavaScript or CSS into web pages, modifying website behaviour, hiding page elements, form auto-fill, scraping page data, intercepting requests, detecting URL changes in SPAs, adding keyboard shortcuts to websites, tab audio control, or TypeScript userscripts. Covers all header tags (@match, @grant, @require, @run-in), GM_* synchronous APIs, GM.* promise-based APIs (recommended for new scripts), batch storage (GM.getValues/setValues v5.3+), binary data support (v5.4+), TypeScript setup via @types/tampermonkey, security sandboxing, and cross-browser compatibility (Chrome, Firefox, Edge). Do NOT use for Selenium/Puppeteer automation, browser extensions (WebExtensions/MV3), or server-side scripts.
95google-tagmanager
Comprehensive Google Tag Manager guide covering container setup, tags, triggers, variables, data layer, debugging, custom templates, and API automation. Use when working with GTM implementation, configuration, optimisation, troubleshooting, or any GTM-related tasks.
81google-apps-script
Comprehensive guide for Google Apps Script development covering all built-in services (SpreadsheetApp, DocumentApp, GmailApp, DriveApp, CalendarApp, FormApp, SlidesApp), triggers, authorization, error handling, and performance optimization. Use when automating Google Sheets operations, creating Google Docs, managing Gmail/email, working with Google Drive files, automating Calendar events, implementing triggers (time-based, event-based), building custom functions, creating add-ons, handling OAuth scopes, optimizing Apps Script performance, working with UrlFetchApp for API calls, using PropertiesService for persistent storage, or implementing CacheService for temporary data. Covers batch operations, error recovery, and JavaScript ES6+ runtime.
74shopify-theme-dev
Complete theme development guide including file structure, JSON templates, sections, snippets, settings schema, and Online Store 2.0 architecture. Use when creating Shopify themes, organizing theme files, building sections and blocks, working with .json template files, configuring settings_schema.json, creating snippets, or implementing theme customization features.
23shopify-developer
Complete Shopify development reference for Liquid templating, theme development (OS 2.0), GraphQL Admin API, Storefront API, custom app development, Shopify Functions, Hydrogen, performance optimisation, and debugging. Use when working with .liquid files, creating theme sections and blocks, writing GraphQL queries or mutations for Shopify, building Shopify apps with CLI and Polaris, implementing cart operations via Ajax API, optimising Core Web Vitals for Shopify stores, debugging Liquid or API errors, configuring settings_schema.json, accessing Shopify objects (product, collection, cart, customer), using Liquid filters, creating app extensions, working with webhooks, migrating from Scripts to Functions, or building headless storefronts with Hydrogen and React Router 7. Covers API version 2026-01.
17google-analytics
Comprehensive Google Analytics 4 guide covering property setup, events, custom events, recommended events, custom dimensions, user tracking, audiences, reporting, BigQuery integration, gtag.js implementation, GTM integration, Measurement Protocol, DebugView, privacy compliance, and data management. Use when working with GA4 implementation, tracking, analysis, or any GA4-related tasks.
14