关于 ecommerce-caching
面向电商开发者的缓存实施技能,覆盖 CDN、Redis 和整页缓存,并处理个性化内容与商品、价格、库存变化时的缓存失效。
主要功能
- 01规划 CDN、应用与整页缓存
- 02使用 Redis 缓存商品数据
- 03区分个性化页面与可缓存内容
- 04根据商品、价格和库存变化清理缓存
- 05针对 Shopify、WooCommerce 等平台提供配置
适用场景
- 01加速商品和集合页面
- 02为店面设计 CDN 与边缘缓存
- 03优化促销期间的首字节时间
- 04商品或价格更新后及时清理缓存
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill ecommerce-caching安装后在 Claude Code 中输入/ecommerce-caching确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/cache-control-headers-and-cdn-surrogate-/criteria.jsontask.mdevals/event-driven-cache-invalidation-and-pers/criteria.jsontask.mdevals/redis-application-caching-with-ioredis/criteria.jsontask.md详细介绍
电商缓存
概述
为电商应用实现多层缓存,涵盖 CDN 边缘缓存、应用级缓存(Redis)和支持购物车感知失效的整页缓存。本技能旨在解决电商页面缓存面临的独特挑战:个性化内容(购物车数量、登录状态)、频繁变化的库存和价格,以及商品或价格变更时需要立即清除缓存。
何时使用此技能
- 商品页和商品集合页因数据库查询和 API 调用而加载缓慢时
- 为店面实施 CDN 或边缘缓存策略时
- 为商品数据、库存和会话管理添加 Redis 缓存时
- 构建可响应商品、价格或库存变更的缓存失效逻辑时
- 为高流量促销活动优化首字节时间(TTFB)时
核心说明
步骤 1:确定你的平台以及可控范围
| 平台 | 为你管理的内容 | 你可以控制的内容 |
|---|---|---|
| Shopify | CDN、整页缓存、服务器基础设施 | Liquid 模板效率、图片优化、应用性能、自定义路由上的 HTTP 缓存标头 |
| WooCommerce | 无(服务器由你管理) | 所有内容:页面缓存、对象缓存、CDN、数据库查询 |
| BigCommerce | CDN、整页缓存、服务器基础设施 | 主题性能、图片优化、应用开销 |
| Custom / Headless | 无(服务器由你管理) | 所有内容:CDN 配置、Redis、Varnish、应用缓存、缓存失效 |
步骤 2:平台专属缓存配置
Shopify
Shopify 会自动处理 CDN 和整页缓存。你可以优化以下方面:
主题性能(Liquid):
- 减少商品模板和商品集合模板中的 Liquid
{% render %}调用数量——每个 render 标签都会增加渲染时间 - 在 Online Store → Themes → Edit code 中,避免反复查询商品目录的循环;使用
{% liquid %}块编写条件逻辑 - 在主题设置中启用 Shopify 的商品图片延迟加载——这样可避免页面下方的图片阻塞页面加载
应用性能:
- 在 Apps → App usage 中检查已安装的应用,或使用 Shopify's Storefront Performance dashboard(Online Store → Themes → View report)进行检查
- 会在每个页面(尤其是结账页面)注入脚本的应用可能会使每次页面加载增加 200–500ms
- 移除未使用的应用——即使处于非活动状态,只要安装了脚本,也会拖慢店面
- 对于会添加店面 JavaScript 的应用,请检查其设置中是否支持延迟加载(页面可交互后再加载)
CDN 图片优化: Shopify 会自动通过其 CDN 提供图片,并进行 WebP 转换和响应式尺寸调整。请按以下方式确保使用了这些功能:
- 始终使用 Shopify 的 Liquid
img_url过滤器生成图片 URL——这样会通过 Shopify CDN 提供图片 - 为 Liquid 中的所有
<img>标签添加width和height属性,以防止累积布局偏移 - 在 Online Store → Themes → Customize → Theme settings → Images 中,确认已启用延迟加载
WooCommerce
WooCommerce 运行在你的托管基础设施上。请实现三层缓存堆栈:
第 1 层:PHP 对象缓存(Redis)
- 你的托管服务必须支持 Redis(WP Engine、Kinsta、Cloudways 以及大多数 WordPress 托管服务都支持——请查看控制面板)
- 安装 Redis Object Cache(免费,可在 wordpress.org 获取)
- 前往 Settings → Redis,然后点击 Enable Object Cache
- 该缓存会将所有 WordPress 和 WooCommerce 数据库查询缓存在 Redis 中;大多数商店的数据库负载都会降低 60–80%
第 2 层:页面缓存
- 安装 WP Rocket(每年 $59,是效果最好的 WordPress 页面缓存插件)或 LiteSpeed Cache(免费,适用于使用 LiteSpeed 的托管服务)
- 在 WP Rocket 中:前往 Cache → Enable caching for logged-in WordPress users——请保持此选项关闭;缓存页面应仅提供给匿名访客
- 启用 Preload cache:WP Rocket 会抓取并预缓存你的产品页和商店页面
- 如果移动端和桌面端布局差异很大,请启用 Separate cache file for mobile
第 3 层:CDN
- 将 Cloudflare(提供免费套餐)配置为你的 CDN:
- 将域名添加到 Cloudflare,并将 DNS 指向 Cloudflare 的 nameservers
- 安装 Cloudflare WordPress 插件,以便从 wp-admin 清除缓存
- 在 Cloudflare 控制面板中将 Caching level 设为 Standard
- 在 WP Rocket 的 CDN 设置中启用 Cloudflare 集成——这样当你更新产品时,WP Rocket 会清除 Cloudflare 缓存
- 或者使用 BunnyCDN(带宽费用为每月 $1),它可与 WP Rocket 原生集成
WooCommerce 的缓存失效:
WP Rocket 和 LiteSpeed Cache 会在通过 wp-admin 更新产品时自动清除缓存页面。对于程序化更新,请将以下内容添加到子主题的 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
}
});
自定义 / 无头架构
无头 Node.js 店面使用的缓存层:
Browser → CDN (Cloudflare/Fastly) → Application server → Redis → Database
[Static assets: 1yr] [Product data: 5min] [Catalog queries]
[Product pages: 5min]
[Cart: never]
按内容类型设置 HTTP 缓存标头:
// 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 产品缓存(旁路缓存模式):
// 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);
}
个性化内容(购物车数量、库存)在缓存页面加载后由客户端加载:
<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') });
}
事件驱动的缓存失效:
// 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——在后台获取新版本的同时,立即提供缓存内容;这可以消除用户遇到的缓存未命中延迟 - 缓存页面,并在客户端填充个性化内容——为匿名访客缓存完整 HTML,并通过快速的
no-storeAPI 调用加载购物车数量 / 库存 - 仅在库存状态变化时失效,而不是每次库存更新都失效——数量从 50 变为 49 无需清除缓存;从 1 变为 0(缺货)则需要
- 移除跟踪参数以提高 CDN 命中率——
?utm_source=email&utm_campaign=spring会创建唯一的缓存条目;请在 CDN 或 URL 规范化过程中移除这些参数 - 监控缓存命中率——CDN 命中率目标为 90% 以上;使用
X-Cache响应标头识别频繁的缓存未命中
常见问题
| 问题 | 解决方案 |
|---|---|
| 缓存页面上的购物车数量显示为 0 | 切勿将购物车数量嵌入缓存的 HTML;页面加载后,通过快速的 no-store API 端点在客户端加载 |
| 产品价格已更新,但 CDN 仍显示旧价格 | 实现事件驱动的失效机制,在价格变化时清除 CDN;不要只依赖 TTL 到期 |
| 缓存过期时出现惊群效应 | 使用 stale-while-revalidate,这样只有一个请求会重新验证,其他请求则获取略微过期的内容 |
| CDN 命中率低 | 通过移除跟踪参数规范化 URL;尽量减少 Vary 标头(例如,Vary: Cookie 实际上会禁用缓存) |
| Redis 内存无限增长 | 在 Redis 配置中设置 maxmemory 和 maxmemory-policy allkeys-lru;监控驱逐率 |
相关技能
- @flash-sale-scaling
- @edge-commerce
- @database-optimization-commerce
- @monitoring-alerting-commerce
评论
支持 Markdown