Performance optimization for Shopify stores including theme speed optimization, image optimization, JavaScript and CSS minification, lazy loading, CDN usage, caching strategies, Liquid template performance, and Core Web Vitals improvement. Use when optimizing store speed, reducing load times, improving Lighthouse scores, optimizing images, implementing lazy loading, reducing JavaScript bloat, or improving Core Web Vitals metrics (LCP, FID, CLS).
/plugin marketplace add henkisdabro/wookstar-claude-code-plugins/plugin install shopify-developer@wookstar-claude-code-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Expert guidance for optimizing Shopify store performance including theme speed, asset optimization, and Core Web Vitals.
Invoke this skill when:
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"
>
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
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 }}">
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;
}
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 %}
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>
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);
})
);
});
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>
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'] });
Images:
img_url filter with appropriate sizessrcsetloading="lazy" to below-fold imageswidth and height attributesJavaScript:
CSS:
@importFonts:
font-display: swapThird-Party:
Liquid:
limit instead of manual breaksrender over includeCore Web Vitals:
{# 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">
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.