commerceモジュール(EC機能)
商品ページ・カテゴリ一覧・キーワード検索・カート・チェックアウトを提供する、オプトインのECモジュールです。取り外し可能な設計になっており、EC機能が不要なサイトではsrc/modules/commerce/とsrc/pages/[locale]/commerce/一式を丸ごと削除できます。
commerceモジュールは多言語対応(i18n)がオプトインされている前提の実装です。
ディレクトリ構成
src/modules/commerce/
blocks/
buy-box/ # カート追加ボタン(PDP用)
product-gallery/ # 商品画像ギャラリー(PDP用)
related-products/ # 関連商品(cards Blockを内部で再利用)
lib/
cart.ts # カート状態管理(localStorage)
favorites.ts # お気に入り状態管理(localStorage、会員限定)
favorite-button.ts # お気に入りボタンの共通UIロジック
acdl-bridge.ts # cart:change → ACDL変換
locale.ts # ルートロケール↔商品ロケールの対応表
content/
products/*.yaml # 商品データ(1商品1ファイル)
taxonomy/categories-<locale>.yaml # カテゴリキー→表示名
src/pages/[locale]/commerce/
index.astro
detail/[slug].astro
products-index.json.ts # 検索用JSONインデックス
category/index.astro
category/[category].astro
search.astro
cart/index.astro
cart/checkout/index.astro
confirmation.astro
order.astro
member/index.astro # マイページ(member.enabled時のみ)
member/favorites.astro # お気に入り一覧(member.enabled時のみ)
商品データ
1商品 = 1ファイル(YAML)。content/products/<slug>.yamlに配置します。
sku: WKDY-SHU-001
images:
- /images/products/commute-running-shoes.png
categories: [shoes]
tags: [running, commute]
stock: in_stock
prices:
ja-JP: { currency: JPY, amount: 18000 }
en-US: { currency: USD, amount: 129.99 }
translations:
ja-JP: { title: "コミュートランニングシューズ", description: "通勤ラン需要に応える軽量設計の..." }
en-US: { title: "Commute Running Shoes", description: "Lightweight shoes built for the run-commute..." }
スキーマ(src/content.config.tsのproductsコレクション)の主なフィールドは次の通りです。
sku(string) — 商品コード。ロケール非依存images(string配列、最低1件) — 画像パスの配列categories(string配列、最低1件) — カテゴリキー(taxonomy辞書のキーと対応)tags(string配列、既定[]) — 検索用キーワードstock(in_stock|out_of_stock、既定in_stock) — 在庫状態prices({ [locale]: { currency, amount } }) — ロケール別価格。キーはja-JP/en-UStranslations({ [locale]: { title, description? } }) — ロケール別商品名・説明文
SKU・画像・カテゴリキー・在庫状態はロケール非依存です。価格と表示文言だけがロケール別に持たれます(1商品1ファイルの原則を保つため)。
npm run new:product -- <slug>で商品データの雛形を生成できます。
カテゴリ表示名
content/taxonomy/categories-<locale>.yamlにカテゴリキー→表示名の対訳を定義します。
# categories-ja.yaml
wear: ウェア
shoes: シューズ
bags: バッグ
accessories: アクセサリー
ページ一覧
/<locale>/commerce— コマーストップ。検索・カテゴリ・カートへの導線/<locale>/commerce/detail/[slug]— PDP(商品詳細)。固定Astroテンプレート/<locale>/commerce/category— カテゴリTOP。全カテゴリの一覧/<locale>/commerce/category/[category]— カテゴリ別一覧。cardsBlockを再利用して表示/<locale>/commerce/campaign— キャンペーンTOP・個別キャンペーン。content/pages/配下のBlock記法ページ(固定テンプレートではなく、このCMSの通常ページとして書かれている)/<locale>/commerce/search— キーワード検索。静的1ページ+クライアントJS/<locale>/commerce/products-index.json— 検索用の軽量JSONインデックス(APIエンドポイント)/<locale>/commerce/cart— カート内容の一覧・数量変更・削除/<locale>/commerce/cart/checkout— ダミーのチェックアウトフォーム(実際の決済処理はしない)/<locale>/commerce/confirmation— 注文確認。「注文を確定する」を押すまで注文は確定しない/<locale>/commerce/order— 注文完了(サンクス)ページ/<locale>/commerce/member— マイページ(ハブ)。ログイン中会員向けの導線をまとめる。site.config.tsのmember.enabledがtrueの場合のみ生成される/<locale>/commerce/member/favorites— お気に入り一覧・削除。同じくmember.enabled時のみ生成
マイページ・お気に入り一覧はcommerceモジュールが提供しますが、会員機能そのもの(ログイン・会員発行)は独立したmemberモジュールの役割です。詳しくは会員機能(ログインダミーシステム)を参照してください。
PDPはgetStaticPaths()でロケール×商品の直積を生成します。
export async function getStaticPaths() {
const products = await getCollection('products');
return ['ja', 'en'].flatMap((locale) =>
products.map((product) => ({ params: { locale, slug: product.id }, props: { product } })),
);
}
カート(cart.ts)
src/modules/commerce/lib/cart.tsがlocalStorageベースのカート状態を管理します。マーケティングツールには一切依存しません(ACDL連携は別レイヤーで行います)。
type CartItem = {
slug: string; title: string; price: number; currency: 'JPY' | 'USD';
image: string; quantity: number; sku: string; categories: string[];
};
getCart(locale: string): Cart
addItem(locale: string, item: Omit<CartItem, 'quantity'>, quantity?: number): Cart
updateQuantity(locale: string, slug: string, quantity: number): Cart // 0以下で削除
removeItem(locale: string, slug: string): Cart
clearCart(locale: string): Cart
locale引数は商品ロケール表記(ja-JP/en-US)。localStorageキーはastro-table:cart:<locale>で、ロケールごとに独立したカートになります(通貨が混在しないようにするため)- 操作のたびに
window.dispatchEvent(new CustomEvent('cart:change', { detail }))を発火します。detail.actionはadd/update/remove/clear/syncのいずれかです - 複数タブを開いて操作するケースに対応するため、他タブでの変更は
storageイベント経由で検知し、action: 'sync'として再発火します
購読側の実装例です(cart/index.astroと同じ考え方)。
window.addEventListener('cart:change', (event) => {
if (event.detail.locale === cartLocale) render();
});
ACDL連携(中央集権ブリッジ)
src/modules/commerce/lib/acdl-bridge.tsがcart:changeイベントを購読し、Adobe Client Data Layer(ACDL)へのpushに変換します。cart.ts自身はACDLの存在を知りません。
cart.ts (状態管理、ベンダー非依存)
↓ window.dispatchEvent('cart:change', ...)
acdl-bridge.ts (ACDL特化の変換層)
↓ window.adobeDataLayer.push({ event: 'add_to_cart', ... })
adobe-client-data-layer
add→add_to_cart、remove→remove_from_cart、updateは変更前後の数量差分(delta)を見てadd_to_cart/remove_from_cartのどちらかにマッピングします。clear/syncはpushしません。
acdl-bridge.tsはどこからも自動では読み込まれません。カート変更が起こりうるページ(現状commerce/detail/[slug].astroとcommerce/cart/index.astro)で明示的にimportする必要があります。新しくカート操作を追加するページを作る場合は、このimportを忘れないでください。ACDL全体の設計はAdobe Client Data Layer連携を参照してください。
buy-box Block
PDPの「カートに入れる」ボタンです。cart.tsのaddItem()を呼び、成功時にボタンの見た目の変化と「カートに追加しました」というフィードバックメッセージ(aria-live="polite")を数秒間表示します。ロケールに応じた価格表示はIntl.NumberFormatで行います。
お気に入り(favorites.ts)
会員限定機能です。src/modules/commerce/lib/favorites.tsがmemberモジュールのgetCurrentMemberId()を参照します(commerce→memberの片方向依存。commerceモジュール内で唯一memberモジュールに依存する箇所です)。未ログイン時はaddFavorite/removeFavoriteがnullを返し、操作を拒否します。
getFavorites(memberId: string): string[]
isFavorite(memberId: string, slug: string): boolean
addFavorite(slug: string): string[] | null // 未ログイン時はnull
removeFavorite(slug: string): string[] | null
favorite-button.tsが「ログイン時のみ表示・クリックでトグル」というUI共通ロジックを提供し、PDP(commerce/detail/[slug].astro)で使用しています。一覧・削除はcommerce/member/favorites.astroが担当し、commerce/member/index.astro(マイページ)配下のページとして位置づけられています。
会員機能(member.enabled)がOffのサイトでは、commerce/member配下のページ自体が生成されず、お気に入り機能も実質的に使えません。
commerceモジュールを削除する
EC機能が不要なサイトを作る場合は、以下を削除してください。
src/modules/commerce/を削除src/pages/[locale]/commerce/一式を削除content/products/、content/taxonomy/を削除し、src/content.config.tsからproducts/taxonomyコレクションの登録を削除nav/<locale>.mdからカート・検索・カテゴリへの導線を削除
@adobe/adobe-client-data-layer本体やACDLのpage名前空間の初期化はEC非依存のコア機能として残ります。