ecommerce-caching について
EC 開発者向けのキャッシュ実装スキルです。CDN、Redis、ページ全体のキャッシュを扱い、個人向けコンテンツや商品・価格・在庫の変更に伴う無効化にも対応します。
主な機能
- 01CDN・アプリ・ページ全体のキャッシュ設計
- 02Redis による商品データのキャッシュ
- 03個人向け情報とキャッシュ可能な内容の分離
- 04商品・価格・在庫変更時のキャッシュ無効化
- 05Shopify や 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詳細
Eコマースのキャッシュ
概要
CDN エッジキャッシュ、アプリケーションレベルのキャッシュ(Redis)、カートを考慮した無効化を備えたフルページキャッシュを網羅し、Eコマースアプリケーション向けの多層キャッシュを実装します。このスキルでは、コマースページのキャッシュに固有の課題に対処します。カート数やログイン状態などのパーソナライズされたコンテンツ、頻繁に変わる在庫や価格、商品や価格が変更された際にキャッシュを即座にパージする必要性などが含まれます。
このスキルを使用する場面
- データベースクエリや API 呼び出しが原因で、商品ページやコレクションページの表示が遅い場合
- ストアフロント向けの CDN またはエッジキャッシュ戦略を実装する場合
- 商品データ、在庫、セッション管理に Redis キャッシュを追加する場合
- 商品、価格、在庫の変更に応じて動作するキャッシュ無効化ロジックを構築する場合
- アクセスの多いセールイベントで、初回バイトまでの時間(TTFB)を最適化する場合
主な手順
ステップ 1:プラットフォームと制御できる範囲を確認する
| プラットフォーム | 管理が任されているもの | 制御できるもの |
|---|---|---|
| Shopify | CDN、フルページキャッシュ、サーバーインフラストラクチャ | Liquid テンプレートの効率、画像の最適化、アプリのパフォーマンス、カスタムルートの HTTP キャッシュヘッダー |
| WooCommerce | なし(サーバーは自分で所有) | すべて:ページキャッシュ、オブジェクトキャッシュ、CDN、データベースクエリ |
| BigCommerce | CDN、フルページキャッシュ、サーバーインフラストラクチャ | テーマのパフォーマンス、画像の最適化、アプリのオーバーヘッド |
| カスタム/ヘッドレス | なし(サーバーは自分で所有) | すべて:CDN の設定、Redis、Varnish、アプリケーションキャッシュ、無効化 |
ステップ 2:プラットフォーム固有のキャッシュ設定
Shopify
Shopify は CDN とフルページキャッシュを自動的に処理します。最適化できるポイントは次のとおりです。
テーマのパフォーマンス(Liquid):
- 商品テンプレートやコレクションテンプレートで Liquid の
{% render %}呼び出し数を減らします。render タグを使うたびにレンダリング時間が増加します - オンラインストア → テーマ → コードを編集で、カタログを繰り返しクエリするループを避け、条件ロジックには
{% liquid %}ブロックを使用します - テーマ設定で Shopify の遅延読み込みを商品画像に対して有効にします。これにより、ページ下部の画像がページの読み込みを妨げるのを防げます
アプリのパフォーマンス:
- アプリ → アプリの使用状況、または Shopify のストアフロントパフォーマンスダッシュボード(オンラインストア → テーマ → レポートを表示)で、インストール済みのアプリを確認します
- すべてのページ(特にチェックアウト)にスクリプトを挿入するアプリは、ページの読み込みごとに 200~500ms 追加することがあります
- 使用していないアプリを削除します。インストール済みのスクリプトがあるアプリは、無効になっていてもストアの速度を低下させます
- ストアフロントに JavaScript を追加するアプリについては、設定で遅延読み込み(ページの操作が可能になった後に読み込む)に対応しているか確認します
CDN による画像の最適化: Shopify は CDN 経由で画像を自動的に配信し、WebP への変換とレスポンシブサイズの調整を行います。これを確実に利用するには、次の手順に従います。
- Shopify CDN 経由で画像 URL を生成するため、必ず Shopify の Liquid
img_urlフィルターを使用します - 累積レイアウトシフトを防ぐため、Liquid のすべての
<img>タグにwidth属性とheight属性を追加します - オンラインストア → テーマ → カスタマイズ → テーマ設定 → 画像で、遅延読み込みが有効になっていることを確認します
WooCommerce
WooCommerce はホスティングインフラストラクチャ上で動作します。3 層のキャッシュスタックを実装します。
レイヤー 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 のネームサーバーに向けます
- Cloudflare WordPress プラグインをインストールし、wp-admin からキャッシュをパージできるようにします
- Cloudflare ダッシュボードで Caching level を Standard に設定します
- WP Rocket の CDN 設定で Cloudflare 統合を有効にします。これにより、商品を更新した際に WP Rocket が Cloudflare キャッシュをパージできます
- または、WP Rocket とネイティブに統合できる BunnyCDN(帯域幅に対して月額 1 ドル)を使用します
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 を使用すると、他のすべてのリクエストに少し古いコンテンツを返しながら、1 つのリクエストだけが再検証を行います |
| CDN のヒット率が低い | トラッキングパラメーターを取り除いて URL を正規化します。Vary ヘッダーを最小限にします(例:Vary: Cookie は実質的にキャッシュを無効にします) |
| Redis のメモリ使用量が際限なく増える | Redis の設定で maxmemory と maxmemory-policy allkeys-lru を設定し、エビクション率を監視します |
関連スキル
- @flash-sale-scaling
- @edge-commerce
- @database-optimization-commerce
- @monitoring-alerting-commerce