skills/infrastructure-performance/ecommerce-caching/SKILL.md
Improve store performance with a layered caching strategy — CDN edge caching, Redis application cache, and smart cart-aware invalidation
npx skillsauth add finsilabs/awesome-ecommerce-skills ecommerce-cachingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Implement multi-layer caching for e-commerce applications covering CDN edge caching, application-level caching (Redis), and full-page caching with cart-aware invalidation. This skill addresses the unique challenges of caching commerce pages — personalized content (cart count, logged-in state), frequently changing inventory and prices, and the need for instant cache purging when products or prices change.
| Platform | What's Managed For You | What You Control | |----------|----------------------|-----------------| | Shopify | CDN, full-page caching, server infrastructure | Liquid template efficiency, image optimization, app performance, HTTP cache headers on custom routes | | WooCommerce | Nothing (you own the server) | Everything: page cache, object cache, CDN, database queries | | BigCommerce | CDN, full-page caching, server infrastructure | Theme performance, image optimization, app overhead | | Custom / Headless | Nothing (you own the server) | Everything: CDN configuration, Redis, Varnish, application cache, invalidation |
Shopify handles CDN and full-page caching automatically. Your optimization levers are:
Theme performance (Liquid):
{% render %} calls in your product and collection templates — each render tag adds rendering time{% liquid %} blocks for conditional logicApp performance:
CDN image optimization: Shopify automatically serves images via its CDN with WebP conversion and responsive sizing. To ensure you're using this:
img_url filter to generate image URLs — this routes through the Shopify CDNwidth and height attributes to all <img> tags in your Liquid to prevent Cumulative Layout ShiftWooCommerce runs on your hosting infrastructure. Implement a three-layer caching stack:
Layer 1: PHP Object Cache (Redis)
Layer 2: Page Cache
Layer 3: CDN
Cache invalidation for WooCommerce:
WP Rocket and LiteSpeed Cache automatically purge cached pages when products are updated via the wp-admin. For programmatic updates, add this to your child theme's functions.php:
// Purge product page cache when inventory or price changes
add_action('woocommerce_product_set_stock', function($product) {
if (function_exists('rocket_clean_post')) {
rocket_clean_post($product->get_id()); // WP Rocket
}
if (class_exists('LiteSpeed_Cache_API')) {
LiteSpeed_Cache_API::purge_post($product->get_id()); // LiteSpeed
}
});
Cache layers for a headless Node.js storefront:
Browser → CDN (Cloudflare/Fastly) → Application server → Redis → Database
[Static assets: 1yr] [Product data: 5min] [Catalog queries]
[Product pages: 5min]
[Cart: never]
HTTP cache headers by content type:
// middleware/cacheHeaders.js
export function setCacheHeaders(req, res, next) {
const path = req.path;
// Cart, checkout, account — never cache (personalized)
if (path.startsWith('/cart') || path.startsWith('/checkout') || path.startsWith('/account')) {
res.setHeader('Cache-Control', 'private, no-store');
return next();
}
// Product pages — 5 min at CDN, serve stale while revalidating
if (path.match(/^\/products\/[\w-]+$/)) {
res.setHeader('Cache-Control', 'public, max-age=60, s-maxage=300, stale-while-revalidate=600');
res.setHeader('Surrogate-Key', `product product-${path.split('/').pop()}`);
return next();
}
// Collection pages
if (path.match(/^\/collections\/[\w-]+$/)) {
res.setHeader('Cache-Control', 'public, max-age=120, s-maxage=600, stale-while-revalidate=1200');
res.setHeader('Surrogate-Key', `collection collection-${path.split('/').pop()}`);
return next();
}
// Static assets with content hash in filename — immutable for 1 year
if (path.match(/\.(js|css|png|jpg|webp|woff2|svg)(\?|$)/)) {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
return next();
}
next();
}
Redis product cache (cache-aside pattern):
// lib/productCache.js
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export async function getCachedProduct(productId, fetchFn) {
const key = `product:${productId}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const product = await fetchFn();
// Non-blocking write — serve response immediately
redis.setex(key, 300, JSON.stringify(product)).catch(() => {});
return product;
}
export async function invalidateProduct(productId, productSlug, collectionIds = []) {
const keys = [
`product:${productId}`,
`product:slug:${productSlug}`,
...collectionIds.map(id => `collection:${id}`),
];
await redis.del(...keys);
}
Personalized content (cart count, inventory) loaded client-side after cached page:
<!-- Serve the cached page with placeholder for personalized data -->
<span id="cart-count" data-hydrate="true">0</span>
<script>
// Load personalized data after the cached page renders
fetch('/api/session-data', { credentials: 'include' })
.then(r => r.json())
.then(data => {
document.getElementById('cart-count').textContent = data.cartCount;
});
</script>
// GET /api/session-data — NOT cached, reads from Redis
export async function sessionData(req, res) {
res.setHeader('Cache-Control', 'private, no-store');
const sessionId = req.cookies.session_id;
const cartCount = sessionId ? await redis.get(`cart:count:${sessionId}`) : '0';
res.json({ cartCount: parseInt(cartCount ?? '0') });
}
Event-driven cache invalidation:
// React to product/price changes from your webhook or admin
export async function onProductUpdated({ productId, productSlug, collectionIds }) {
// 1. Invalidate Redis cache
await invalidateProduct(productId, productSlug, collectionIds);
// 2. Purge CDN (Cloudflare example)
await fetch(`https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${CF_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ tags: [`product-${productSlug}`] }), // tag-based purge (Cloudflare Enterprise)
// For free/Pro Cloudflare, use files: [`https://store.com/products/${productSlug}`]
});
}
stale-while-revalidate — serve cached content instantly while fetching a fresh version in the background; this eliminates cache miss latency for usersno-store API call?utm_source=email&utm_campaign=spring creates unique cache entries; strip these at the CDN or in your URL normalizationX-Cache response headers to identify frequent cache misses| Problem | Solution |
|---------|----------|
| Cart count shows 0 on cached pages | Never embed cart count in cached HTML; load it client-side via a fast no-store API endpoint after page load |
| Product price updated but CDN shows old price | Implement event-driven invalidation that purges CDN on price change; don't rely on TTL expiration alone |
| Thundering herd when cache expires | Use stale-while-revalidate so only one request revalidates while all others get slightly stale content |
| Low CDN hit rate | Normalize URLs by stripping tracking params; minimize Vary headers (e.g., Vary: Cookie effectively disables caching) |
| Redis memory grows unbounded | Set maxmemory and maxmemory-policy allkeys-lru in Redis config; monitor eviction rate |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion