mcpField
E
ecommerce-caching

ecommerce-caching

github.com/finsilabs开发6079 次安装5 天前更新
代码仓库

关于 ecommerce-caching

面向电商开发者的缓存实施技能,覆盖 CDN、Redis 和整页缓存,并处理个性化内容与商品、价格、库存变化时的缓存失效。

主要功能

  1. 01规划 CDN、应用与整页缓存
  2. 02使用 Redis 缓存商品数据
  3. 03区分个性化页面与可缓存内容
  4. 04根据商品、价格和库存变化清理缓存
  5. 05针对 Shopify、WooCommerce 等平台提供配置

适用场景

  1. 01加速商品和集合页面
  2. 02为店面设计 CDN 与边缘缓存
  3. 03优化促销期间的首字节时间
  4. 04商品或价格更新后及时清理缓存

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill ecommerce-caching

安装后在 Claude Code 中输入/ecommerce-caching确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/cache-control-headers-and-cdn-surrogate-/
criteria.json
task.md
evals/event-driven-cache-invalidation-and-pers/
criteria.json
task.md
evals/redis-application-caching-with-ioredis/
criteria.json
task.md

详细介绍

电商缓存

概述

为电商应用实现多层缓存,涵盖 CDN 边缘缓存、应用级缓存(Redis)和支持购物车感知失效的整页缓存。本技能旨在解决电商页面缓存面临的独特挑战:个性化内容(购物车数量、登录状态)、频繁变化的库存和价格,以及商品或价格变更时需要立即清除缓存。

何时使用此技能

  • 商品页和商品集合页因数据库查询和 API 调用而加载缓慢时
  • 为店面实施 CDN 或边缘缓存策略时
  • 为商品数据、库存和会话管理添加 Redis 缓存时
  • 构建可响应商品、价格或库存变更的缓存失效逻辑时
  • 为高流量促销活动优化首字节时间(TTFB)时

核心说明

步骤 1:确定你的平台以及可控范围
平台为你管理的内容你可以控制的内容
ShopifyCDN、整页缓存、服务器基础设施Liquid 模板效率、图片优化、应用性能、自定义路由上的 HTTP 缓存标头
WooCommerce无(服务器由你管理)所有内容:页面缓存、对象缓存、CDN、数据库查询
BigCommerceCDN、整页缓存、服务器基础设施主题性能、图片优化、应用开销
Custom / Headless无(服务器由你管理)所有内容:CDN 配置、Redis、Varnish、应用缓存、缓存失效
步骤 2:平台专属缓存配置

Shopify

Shopify 会自动处理 CDN 和整页缓存。你可以优化以下方面:

主题性能(Liquid):

  1. 减少商品模板和商品集合模板中的 Liquid {% render %} 调用数量——每个 render 标签都会增加渲染时间
  2. 在 Online Store → Themes → Edit code 中,避免反复查询商品目录的循环;使用 {% liquid %} 块编写条件逻辑
  3. 在主题设置中启用 Shopify 的商品图片延迟加载——这样可避免页面下方的图片阻塞页面加载

应用性能:

  1. 在 Apps → App usage 中检查已安装的应用,或使用 Shopify's Storefront Performance dashboard(Online Store → Themes → View report)进行检查
  2. 会在每个页面(尤其是结账页面)注入脚本的应用可能会使每次页面加载增加 200–500ms
  3. 移除未使用的应用——即使处于非活动状态,只要安装了脚本,也会拖慢店面
  4. 对于会添加店面 JavaScript 的应用,请检查其设置中是否支持延迟加载(页面可交互后再加载)

CDN 图片优化: Shopify 会自动通过其 CDN 提供图片,并进行 WebP 转换和响应式尺寸调整。请按以下方式确保使用了这些功能:

  1. 始终使用 Shopify 的 Liquid img_url 过滤器生成图片 URL——这样会通过 Shopify CDN 提供图片
  2. 为 Liquid 中的所有 <img> 标签添加 width 和 height 属性,以防止累积布局偏移
  3. 在 Online Store → Themes → Customize → Theme settings → Images 中,确认已启用延迟加载

WooCommerce

WooCommerce 运行在你的托管基础设施上。请实现三层缓存堆栈:

第 1 层:PHP 对象缓存(Redis)

  1. 你的托管服务必须支持 Redis(WP Engine、Kinsta、Cloudways 以及大多数 WordPress 托管服务都支持——请查看控制面板)
  2. 安装 Redis Object Cache(免费,可在 wordpress.org 获取)
  3. 前往 Settings → Redis,然后点击 Enable Object Cache
  4. 该缓存会将所有 WordPress 和 WooCommerce 数据库查询缓存在 Redis 中;大多数商店的数据库负载都会降低 60–80%

第 2 层:页面缓存

  1. 安装 WP Rocket(每年 $59,是效果最好的 WordPress 页面缓存插件)或 LiteSpeed Cache(免费,适用于使用 LiteSpeed 的托管服务)
  2. 在 WP Rocket 中:前往 Cache → Enable caching for logged-in WordPress users——请保持此选项关闭;缓存页面应仅提供给匿名访客
  3. 启用 Preload cache:WP Rocket 会抓取并预缓存你的产品页和商店页面
  4. 如果移动端和桌面端布局差异很大,请启用 Separate cache file for mobile

第 3 层:CDN

  1. 将 Cloudflare(提供免费套餐)配置为你的 CDN:
    • 将域名添加到 Cloudflare,并将 DNS 指向 Cloudflare 的 nameservers
    • 安装 Cloudflare WordPress 插件,以便从 wp-admin 清除缓存
    • 在 Cloudflare 控制面板中将 Caching level 设为 Standard
    • 在 WP Rocket 的 CDN 设置中启用 Cloudflare 集成——这样当你更新产品时,WP Rocket 会清除 Cloudflare 缓存
  2. 或者使用 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-store API 调用加载购物车数量 / 库存
  • 仅在库存状态变化时失效,而不是每次库存更新都失效——数量从 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