キャッシュ戦略まとめ¶
本ドキュメントは、Storefront / Vendure / WordPress 間のキャッシュ方針を整理したものです。
概要¶
- Storefront (Vite SPA): public GET の edge cache opt-in と
revalidationmetadata を管理 - Vendure: Redis があれば Redis キャッシュ、なければ in-memory
- WordPress (WPGraphQL): Storefront/Vendure の二層で TTL キャッシュを管理
現在の実装ステータス¶
現時点では、production 系のキャッシュ戦略実装は完了している。追加方針として、 local / mock では shared cache を明示的に無効化し、さらに 会員制 catalog と公開 editorial を route 単位で分離する。
- Storefront
- 公開 GET の edge cache opt-in / private API の
no-store制御を実装済み - local / mock では shared cache bypass を優先し、
revalidate指定や公開 GET でもno-storeを返す - page route では
/articles/*と/announcements/*だけを認証非依存の公開例外とし、/、/products/*、/campaigns/*など commerce 導線はログイン前提のため edge cache 対象へ広げない - 実装箇所:
apps/storefront/src/lib/http/cache-control.tsapps/storefront/src/worker-api.tsapps/storefront/src/lib/http/cache-control.ts
- Vendure
- WordPress 連携にプロセス内 TTL キャッシュ / inflight dedupe / stale-if-error を実装済み
- 重要 CMS (
settings / menus / banners / rankings) には last-known-good snapshot を shared cache へ保存する実装を追加済み - 実装箇所:
packages/plugins/src/cms-integration/wordpress/wordpress.service.tsapps/vendure-server/src/redis-strategy.ts
- インフラ
- production は
ritsubi-redis-prod - staging は
ritsubi-redis-staging - いずれも Fly private network 経由で
*.internal+REDIS_TLS=falseの dedicated Redis app を使う
未完了なのは 運用上の追従作業 だけで、主に以下を Issue で追跡している。
#453: staging の AWS Secrets Manager 正本追従#455: production の AWS Secrets Manager 正本追従と旧ritsubi-redis-prd整理
Redis と Cloudflare の責務分離¶
同じ「キャッシュ」でも、本リポジトリでは Redis と Cloudflare を別用途で使う。
| 層 | 主用途 | 向いているもの | 向いていないもの |
|---|---|---|---|
| Redis / Vendure shared cache | アプリ内部の共有状態・再起動後も残したい補助データ | session、Vendure shared cache、WordPress last-known-good snapshot、複数インスタンスで共有したい値 | ブラウザ向け静的配信、公開ページのエッジ配信 |
| Cloudflare edge cache | 外向き HTTP 配信の高速化 | /assets/*、公開アセット、明示的に opt-in した公開 GET レスポンス |
認証依存レスポンス、POST API、内部 GraphQL fetch、session 共有 |
原則:
- Redis は origin / app 側の共有キャッシュ
- Cloudflare は edge / 配信キャッシュ
- WordPress を正本とする CMS データのうち、障害時に最後の正常値を返したいものは Redis 側で保持する
- Cloudflare 側は 公開 GET にだけ明示的に opt-in し、private /
auth 系 API は
no-storeを返す - Storefront の page route では
/articles/*と/announcements/*以外を公開面とみなさず、商品・販促・顧客導線の HTML/JSON を edge cache へ載せない - React Dashboard / admin API の asset preview は Cloudflare edge
cache に載せず、 Vendure Server 同居
/assets/で read-after-write の即時性を優先する - HTML / JSON を Cloudflare で積極的にキャッシュする場合は、レスポンスヘッダーに加えて Cloudflare Cache Rules の対象 URL を明示的に絞る
Storefront の環境別キャッシュマトリクス¶
| 層 | production / staging / preview | local / mock | 補足 |
|---|---|---|---|
| 公開 GET の edge cache | 明示 opt-in した route のみ有効 | no-store |
/api/campaigns など認証非依存レスポンスだけを対象にする |
shared fetch metadata (revalidation) |
個別 TTL を付与してよい | 共通 helper が cache: "no-store" を優先 |
resolveStorefrontSharedFetchOptions() を経由する |
| Vite 静的 asset 配信 | /assets/* を長期キャッシュ可 |
/assets/* を配信 |
worker.js + ASSETS binding が静的配信を担う |
| TanStack Query | 通常の staleTime を維持 |
staleTime を短くし、invalidateQueries で最新表示を優先 |
queryKey と invalidate 方針を app 側で統一する |
| private / auth 依存 API | no-store |
no-store |
/commerce/shop-api、顧客系 API、health、revalidate など |
追加ルール:
- local / mock の cache bypass は画面ごとに実装せず、Storefront 共通 helper に集約する。
- 新規の public cache は、Cookie / Authorization 非依存を証明できる導線だけに限定する。
- 認証不要な page route の既定値は増やさず、公開例外は
/articles/*と/announcements/*の editorial 導線に限る。 PUBLIC_VENDURE_FETCH_OPTIONSを使う fetch は Cookie が送信されうるため、truly-public な取得経路を別途用意するまでは cross-request cache を追加しない。
Storefront の取得キャッシュ¶
Storefront 表示データの即時無効化¶
Vendure / WordPress 側の更新を Storefront 表示へ反映する入口は domain 単位の revalidation API に集約する。
- 共通入口:
POST /api/revalidate/storefront-data - header:
x-cms-revalidate-secret: <CMS_REVALIDATE_SECRET> - body:
{"domains":["catalog"],"source":"vendure","reason":"product.updated"} - revision 確認入口:
GET /api/storefront-data-revisions?domains=catalog - domain は
catalog/cms/checkout/customer/config。 catalog更新では Storefront shared cache のstorefront-data:catalogtag version を bump し、browser/session cache key にも revision を含める。- Vendure 側は
StorefrontDataInvalidationServiceが商品・variant・collection 更新イベントを拾い、catalogdomain を revalidate する。 - collection filter 更新は materialized contents 完了後の
CollectionModificationEventを正本にして revalidate し、古い membership を 再取得させない。 - TTL は webhook / revalidate 失敗時の安全網として残す。通常反映は revision bump によって次回 navigation/refetch で最新化する。
CMS コンテンツ連携のキャッシュ設定¶
以下の TTL は production / staging / preview で適用する。
local / mockでは共通 helper がno-storeを優先し、shared cache を使わない。なお、WordPress revalidate webhook での即時反映を保つため、browser の
sessionStorageには CMS fetch 結果や site shell model を保持しない。 public CMS の TTL は server / edge の shared cache にだけ適用し、同一ブラウザ session で footer / header が stale のまま残る状態を避ける。
| 種別 | 取得元 | キャッシュ | 設定箇所 |
|---|---|---|---|
| ストア設定(OGP含む) | WordPress | 24時間 (revalidate: 86400) |
apps/storefront/src/lib/cms/content/store-settings.ts |
| メニュー | WordPress | 1時間 (revalidate: 3600) |
apps/storefront/src/lib/cms/content/menus.ts |
| お知らせ一覧 | WordPress | 1時間 (revalidate: 3600) |
apps/storefront/src/lib/cms/content/announcements.ts |
| お知らせ詳細 | WordPress | 5分 (revalidate: 300) |
apps/storefront/src/lib/cms/content/announcements.ts |
| キャンペーン一覧 | WordPress | 1時間 (revalidate: 3600) |
apps/storefront/src/lib/cms/content/campaigns.ts |
| キャンペーン詳細 | WordPress | 5分 (revalidate: 300) |
apps/storefront/src/lib/cms/content/campaigns.ts |
| メインバナー(ホームヒーロー) | WordPress | 1時間 (revalidate: 3600) |
apps/storefront/src/lib/cms/content/hero-slides.ts |
| 商品リッチ説明 | WordPress | 6時間 (revalidate: 21600) |
apps/storefront/src/lib/cms/content/products.ts |
| 固定ページ | WordPress | 5分 (revalidate: 300) |
apps/storefront/src/lib/cms/content/pages.ts |
OGP 反映¶
- OGP は WordPress のストア設定を参照して metadata helper で反映する
- 実質のキャッシュは ストア設定の
revalidate: 86400に依存 - 設定箇所:
apps/storefront/src/lib/storefront-metadata.ts
Cloudflare edge cache の適用方針¶
- Cloudflare は 公開 GET の配信最適化に限定する
local / mockでは app 側がno-storeを返し、edge cache opt-in を無効化する- 現時点で app 側から edge cache を許可する主対象は、公開 CMS 一覧 API のような 認証非依存レスポンス
campaignsは CMS 由来の本文を持っていても storefront では commerce 導線として扱うため、page route の public cache 対象へ広げないshop-api、顧客階層、CMS revalidate、health check などの 内部 / private API はCache-Control: no-storeを明示する- Cloudflare へは
Cloudflare-CDN-Cache-Control/CDN-Cache-Controlを返し、ブラウザ向けCache-Controlと分離する - 公開アセット配信は Cloudflare 側の得意領域なので、
wp-assets.<domain>のようなアセット専用ホストで長めにキャッシュする - ただし Vendure asset のうち admin API が返す URL は
ec-assets*ではなく same-server/assets/を正本とし、Dashboard 画像アップロード直後の stale 404 を避ける
Storefront static asset delivery(Cloudflare Workers + ASSETS)¶
- 静的 asset は Vite build の
/assets/*とpublic/配下のトップレベルファイルを正本にする。 - Cloudflare Worker (
apps/storefront/worker.js) は/assets/*・/favicon.ico・/manifest.jsonなどのファイル要求をASSETSbinding へそのまま流す。 - それ以外の navigation request は
/へ fallback し、TanStack Router が画面遷移を処理する。 - shared cache の opt-in は HTML 全体ではなく、認証非依存 API と fetch helper の metadata に限定する。
- 詳細手順は storefront-cloudflare-deploy.md を参照する。
Storefront で header 制御を実装済みの route¶
| route | 方針 | 補足 |
|---|---|---|
/api/campaigns |
public edge cache opt-in | production 系のみ public。local / mock では no-store |
/api/customer-hierarchy |
private, no-store |
Cookie / Authorization 依存 |
/api/health/live |
no-store |
Tier 0 Safe Probe(連続ポーリング可) |
/api/health/ready |
no-store |
Tier 0 Safe Probe(連続ポーリング可) |
/api/version |
no-store |
Tier 0 Safe Probe(デプロイ検証) |
/api/health/cms |
no-store |
診断専用。Vendure / WordPress への upstream fan-out を含むため連続ポーリング不可 |
/api/revalidate/cms |
no-store |
invalidation endpoint |
/commerce/shop-api |
private, no-store |
GraphQL proxy / 認証依存 |
/api/health/live,/api/health/ready,/api/versionは Safe Probe として連続ポーリング・外形監視に使用可。/api/health/cmsは CMS 配信経路の診断専用で upstream への fan-out があるため、外形監視の対象にしてはならない。詳細は 監視・運用設計書 — Storefront ヘルスチェックエンドポイント を参照。
Vendure のキャッシュ¶
- Redis が利用可能な場合は Redis を使う(Vendure 全体のキャッシュ層)
- WordPress 連携 (
WordPressService) はプロセス内 TTL キャッシュを利用 - タイムアウト:
WORDPRESS_FETCH_TIMEOUT_MS(既定 5000ms) - TTL:
WORDPRESS_FETCH_CACHE_TTL_SECONDS(既定 300秒) - 上限件数:
WORDPRESS_FETCH_CACHE_MAX_ENTRIES(既定 500) - 同一キーの同時リクエストは inflight Promise を共有
- 再取得失敗時は stale-if-error で古い値を返却
settings / menus / banners / rankingsは last-known-good snapshot も共有キャッシュへ保存する- snapshot TTL:
WORDPRESS_SNAPSHOT_TTL_SECONDS(既定 86400秒) - Redis が有効ならプロセス再起動後も snapshot を再利用できる
- 設定箇所:
packages/plugins/src/cms-integration/wordpress/wordpress.service.ts
Redis を使う理由¶
Vendure -> WordPressの内部 fetch は Cloudflare edge cache の対象にしづらい- session や snapshot は 複数インスタンス間で共有したい
- Storefront Worker の再デプロイ / Vendure 再起動後も値を残したい
- そのため、CMS の last-known-good は Cloudflare KV/D1 ではなく Vendure shared cache (Redis) に寄せる
現在の Redis 利用先¶
ritsubi-ecommerce→ritsubi-redis-prod.internalritsubi-ecommerce-staging→ritsubi-redis-staging.internal- staging も production と同じ Redis 経路を通すため、キャッシュ挙動の検証先として利用できる
WordPress (WPGraphQL)¶
- 連携は Vendure の WordPress プラグイン経由
- 画像URLは
VITE_PUBLIC_WORDPRESS_ASSET_BASE_URLを優先して正規化する(/wp-content/...を Cloudflare 配下のアセットドメインに寄せる) - 設定箇所:
apps/storefront/src/lib/cms/content/url.tsapps/storefront/src/lib/cms/content/content-processor.ts
メモリ消費の最適化 (Memory Optimization)¶
Cloudflare Workers (Edge Runtime) の厳しいメモリ制限 (128MB) と、長時間セッションによるブラウザのメモリ肥大化を防止するための設定です。
Vite 静的 asset キャッシュ (Client-side)¶
ブラウザ側ではハッシュ付き /assets/* を配信し、キャッシュ破棄は build ごとの
fingerprint で吸収します。
- 設定:
apps/storefront/vite.config.ts - 配信:
apps/storefront/worker.js+apps/storefront/wrangler.toml
TanStack Query キャッシュ方針 (Client-side)¶
GraphQL 取得結果は queryKey ごとに管理し、local / mock では stale を短く、更新後は invalidateQueries を正本にして再取得する。
- 設定:
packages/sdk/shop/src/query-client.ts - 既定値:
staleTime: 30_000gcTime: 5 * 60_000refetchOnWindowFocus: false- mutation 後は画面ごとの queryKey を invalidate する
route loader / request-time バルクデータ取得の制限¶
route loader や request-time fetch で大量のデータを一括でメモリへ読み込む処理を制限し、Worker のメモリ超過を防止します。
- CMS コンテンツ取得:
apps/storefront/src/lib/cms/content/products.tsにおいて、インデックス取得のmaxPagesを3(最大 300件) に制限。
Sentry サンプリング¶
パフォーマンス監視データのバッファリングによるメモリ消費を抑制します。
- 設定:
tracesSampleRate: 0.1(Production)
即時反映(Webhook + tag invalidation)¶
- Storefront に
POST /api/revalidate/cmsを実装済み - 認証:
- ヘッダー
x-cms-revalidate-secretまたは bodysecret CMS_REVALIDATE_SECRETと照合- payload:
event:announcement.updated | campaign.updated | page.updated | menu.updated | settings.updated | alltags?: string[](指定時は event より優先)- event→tag:
announcement.* => ["cms","announcements"]campaign.* => ["cms","campaigns"]page.* => ["cms","pages"]menu.* => ["cms","menus"]settings.* => ["cms","settings","banners"]all => ["cms","announcements","campaigns","pages","menus","settings","banners","products","rankings"]
[!IMPORTANT]
POST /api/revalidate/cmsが無効化するのは Storefront の fetch cache であり、Vendure のCmsIntegrationPluginが持つ WordPress fetch cache までは即時には消えません。menu (headerMenu/footerMenu) は plugin 内でキャッシュを bypass しているため例外で、storefront tag 無効化と同時に最新値が反映されます。一方product_detailのように Vendure 経由で WPGraphQL を読むその他の導線では、更新後も最大 5 分(DEFAULT_WORDPRESS_FETCH_CACHE_TTL_SECONDS=300)は古い本文やnullが返ることがあります。2026-04 の production 切り分けでは、次の順で確認すると早く収束しました。
- direct WP GraphQL (
https://cms.../graphql) で記事とstyleModeが見えるか- Vendure
shop-apiで同じwordPressProductDetailが返るか- Storefront の DOM (
product-detail-page-wp-content) に出るか1 が新しいのに 2 が古い場合は cache expiry を待つか、必要に応じて Vendure runtime を更新してください。