Adobe Client Data Layer連携
ページの情報やユーザー操作を、いわゆるタグマネージャー(Adobe Launch、GTM等)に橋渡しするためのデータレイヤーとして、Adobe Client Data Layer(公式npmパッケージ、以下ACDL)を採用しています。
初期化
src/layouts/Base.astroの<head>内、他のどのスクリプトよりも先に初期化します。
<script is:inline define:vars={{ pageContext }}>
window.adobeDataLayer = window.adobeDataLayer || [];
window.adobeDataLayer.push({ page: pageContext });
</script>
<script>
import '@adobe/adobe-client-data-layer/dist/adobe-client-data-layer.min.js';
</script>
window.adobeDataLayerを配列として先に初期化し、そこにpageコンテキストをpushしてから、ACDL本体を読み込みます。ACDL本体は既存の配列を検知して、push/getState/addEventListenerを持つ本来のオブジェクトに拡張します(公式推奨の初期化パターンです)。
window.adobeDataLayer.push({
page: {
pageName: string,
pageType: string, // "top" | "product" | "cart" | ... (frontmatterのpageTypeと同じenum)
locale: string, // 多言語対応時のみ
},
});
pageのpush自体はeventキーを持たない、単なる状態マージのData Objectです。ページコンテキストの存在をタグマネージャーができるだけ早く検知できるよう、あえてhead最速のタイミングでpushしています。
Page Load計測イベント(page loaded)
ACDL公式wiki記載の"event": "page loaded"規約に沿ったEvent Objectは、bodyの一番最後(MemberOverlayの後)で別途pushします。
window.adobeDataLayer.push({ event: 'page loaded' });
pageのpush(head最速)とタイミングを分けているのは、user(会員データ)・view_item等、同ページ内の他のACDLコンテキストが揃うのを待ってから発火させるためです。pageのpush自体をPage View計測のトリガーにすると、まだuser等が揃っていない段階でビーコンが送出されてしまいます。タグマネージャー側でPage View計測をトリガーする際は、pageのpushではなくpage loadedイベントを使ってください。
page loadedは付随データを持たないため、ACDLの仕様上adobeDataLayer:changeでは拾えません。adobeDataLayer:event(またはイベント名そのもの)でリスンする必要があります(add-to-cart等、productListItemsのような付随データを持つイベントはchangeでも拾えます)。
イベントのpush: 2つのパターン
判断基準は「横断的な状態変化(ページ/カート/会員セッション)か、Blockローカルな操作か」です。
パターンB: Block直接push(推奨、まずはこちらを検討)
モーダルの開閉・タブ切り替え・アコーディオンのトグルなど、個々のBlock内で完結する操作は、Blockの<script>から直接pushします。
// src/lib/acdl.ts
export function pushEvent(eventName: string, payload: Record<string, unknown> = {}): void {
window.adobeDataLayer.push({ event: eventName, ...payload });
}
<!-- 例: src/blocks/accordion/index.astro -->
<script>
import { pushEvent } from '../../lib/acdl';
document.querySelectorAll('.accordion__item').forEach((item) => {
item.addEventListener('toggle', () => {
const label = item.querySelector('.accordion__title')?.textContent ?? '';
pushEvent('accordion_toggle', { blockName: 'accordion', label, state: item.open ? 'open' : 'closed' });
});
});
</script>
現時点でこのパターンを使っているBlockはaccordion(accordion_toggle)、modal(modal_toggle。トリガークリックでopen、ネイティブのcloseイベントでclosed。閉じるボタン・背景クリック・ESCキーいずれの経路でもcloseイベントは一律で発火するため、経路ごとに個別のpushEventは不要)、tabs(tabs_switch。タブ切り替えのたびに発火)です。
イベント名の強制フォーマットはありませんが、<blockname>_<action>(例: accordion_toggle)を推奨します。新しいBlockを追加するたびに中央側を修正する必要がなく、「ディレクトリを1つ足すだけ」というBlockの設計思想と整合します。
パターンA: 中央集権ブリッジ(横断的な状態変化)
カート状態の変化、会員のログイン/ログアウト、ページ到達によるライフサイクルイベント(view_item/start-checkout/purchase-complete)など、特定のBlockに閉じない変化は、中央の1箇所にまとめます。commerceモジュールのacdl-bridge.tsとmemberモジュールのacdl-bridge.tsがいずれもこの実例です。
cart.ts (状態管理。ベンダー非依存)
↓ window.dispatchEvent(new CustomEvent('cart:change', ...))
acdl-bridge.ts (ACDL特化のマッピング層)
↓ window.adobeDataLayer.push({ event: 'add-to-cart', ... })
adobe-client-data-layer
状態管理モジュール(cart.tsやmember.ts)自身はACDLの存在を一切知りません。汎用イベント(cart:change、member:login等)を発火するだけです。ツールを乗り換える場合も、書き換えが必要なのはブリッジ層だけで済みます。
中央集権ブリッジのモジュールは、そのイベントが発生しうる全ページで明示的にimportする必要があります。静的サイトはページ遷移のたびに全JSが読み込み直されるため、共通レイアウトから自動で読み込まれるわけではありません(memberモジュールのacdl-bridge.tsは例外的に、member.enabled時に全ページへ差し込まれるMemberOverlay.astroから読み込まれているため、実質的にどのページでもimport漏れが起きません)。
ECライフサイクルイベント(commerceモジュール、XDM Commerceイベント設計準拠)
commerceモジュールを使うサイトでは、以下のイベントが発火します。全イベント共通で、そのECサイト機能に関する計測内容(発生回数・ID・カート情報等)はcommerceキー配下、そのイベント時点の商品情報はproductListItems配列(XDM CommerceのProduct List Items形式) という構成に揃えています。
view_item— PDPページ読み込み時。PDPページの初期化スクリプトが直接pushadd-to-cart— カートに追加/数量増加時。commerceのacdl-bridge.tsがpushremove-from-cart— カートから削除/数量減少時。commerceのacdl-bridge.tsがpushstart-checkout— チェックアウトページ(/commerce/cart/checkout)読み込み時。同ページの初期化スクリプトが直接pushpurchase-complete— 注文完了ページ(/commerce/order)読み込み時。同ページの初期化スクリプトが直接push
view_itemのペイロード例です。
window.adobeDataLayer.push({
event: 'view_item',
commerce: {
productViews: { value: 1, id: string }, // idはイベントごとに一意
},
productListItems: [{ SKU, name, quantity: 1, priceTotal, currencyCode, productImageUrl }],
});
add-to-cartのペイロード例です。
window.adobeDataLayer.push({
event: 'add-to-cart',
commerce: {
productListAdds: { value: 1, id: string }, // idはイベントごとに一意
cart: { cartID: string, cartSource: 'product_detail' },
},
productListItems: [
{ SKU, name, quantity, priceTotal, currencyCode, productImageUrl, productAddMethod: 'add_to_cart_button' },
],
});
priceTotalは単価ではなくその明細行の合計金額(単価 × quantity)です。quantityの意味はイベントごとに異なります。
view_item— 常に1(閲覧イベントなので数量という概念自体がない)add-to-cart/remove-from-cart— 今回追加/削除した数量start-checkout— その時点のカート内数量(カート内全商品をproductListItemsに含める)purchase-complete— 注文された数量(注文に含まれる全商品をproductListItemsに含める。単一商品/複数商品どちらの注文でも配列で統一)
commerce.cart.cartIDはカートの一意識別子(cart.tsがcrypto.randomUUID()で発行し、カートが空になるたび=注文確定時に再発行)です。cartSourceはadd-to-cartのみ、追加が起きた場所を表します(PDPからの追加のみサポートしているため常にproduct_detail)。
purchase-completeのペイロード例です。
window.adobeDataLayer.push({
event: 'purchase-complete',
commerce: {
purchases: { value: 1, id: `purchase-event-${purchaseId}` }, // 送信イベントの重複排除キー
order: {
purchaseID: string, // 販売側で発行した注文ID(業務キー)。commerce.purchases.idとは別管理
currencyCode: string,
priceTotal: number, // 注文全体の合計(商品明細priceTotalの合計と一致させる)
payments: [{ paymentAmount: number, paymentType: string, currencyCode: string, transactionID: string }],
},
cart: { cartID: string },
},
productListItems: [{ SKU, name, quantity, priceTotal, currencyCode }],
});
purchase-completeは以下のリロード起因の二重送信対策をしています。
- 注文確定済みページ(
/commerce/order)でのみ発火する purchaseIDは/commerce/confirmationで注文確定した時点の注文システム発行IDを使う(ページロードのたびに生成し直さない)sessionStorageにpurchaseIDごとの送信済みフラグを残し、同じpurchaseIDに対してはorder.astroのリロード・戻る/進む操作で再度pushしない
詳しくはcommerceモジュールを参照してください。
user名前空間(memberモジュール)
member機能(site.config.tsのmember.enabled)を使うサイトでは、ログイン中の会員情報をuser名前空間としてpushします。担当はsrc/modules/member/lib/acdl-bridge.tsで、member.tsが発火するmember:login/member:logout(ベンダー非依存のCustomEvent)を購読し、変換します。
userのpushには、page loadedと同様にeventキー付きのトリガー用イベントがあります。user(identity)が意味する事象を「操作ログ」と「状態セット」の2つに分けて、別々のイベント名で発火します。
-
ログイン操作(
member:login購読時) —user_login(操作ログ)とset_identity(状態セット)の2つを続けてpushします。どちらも同じuserオブジェクトを伴いますwindow.adobeDataLayer.push({ event: 'user_login', user: { id, ...attributes, email, emailSha256 } }); window.adobeDataLayer.push({ event: 'set_identity', user: { id, ...attributes, email, emailSha256 } });emailは会員登録時に必須のフィールドなので常に含まれ、会員発行ページで設定した属性(attributes)もそのままuserオブジェクトに展開されます。emailSha256は正規化(前後空白除去・小文字化)後のメールアドレスをSHA-256ハッシュ化した16進数文字列で、生のメールアドレスを扱えない連携先向けです -
ページ読み込み時の状態復元(下記) —
set_identityのみpushします。実際のログイン操作ではない(既存セッションを新しいページのdataLayerにも反映しているだけ)ため、user_loginは出しません -
ログアウト操作(
member:logout購読時) —user_logoutのみpushします(user: null)。identityがセットされたわけではないのでset_identityは出しませんwindow.adobeDataLayer.push({ event: 'user_logout', user: null }); -
誰もログインしていない状態でのページ読み込み時 —
window.adobeDataLayer.push({ user: null })(eventキーなし)
いずれのpushもuserキー自体は状態としてマージされるため、タグマネージャー側は「現在ログイン中かどうか、ログイン中なら誰か」を状態として参照しつつ、user_login/user_logout/set_identityをトリガーとしても使えます。
window.adobeDataLayerはページ単位(フルページ遷移で消える)なので、pageコンテキストと同様、ページの読み込み時点で既にログイン中ならuser状態を再pushします(これが上記の「ページ読み込み時の状態復元」)。これが無いと、たとえば会員発行ページのクイックログインから別ページへ遷移した直後に、遷移前のページでpushしたuser情報が新しいページのadobeDataLayerに反映されない、という問題が起こります。
memberモジュールのacdl-bridge.tsは、member.enabled時に全ページへ差し込まれるMemberOverlay.astroから読み込まれています。そのため、ログイン/ログアウトが会員発行ページ・ログインページ・オーバーレイ自身のどこで起きても、確実にACDLへ届きます(中央集権ブリッジのimport漏れが構造的に起きないケースです)。会員機能全体の詳しい説明は会員機能(ログインダミーシステム)を参照してください。
マーケティングタグの注入(タグマネージャー本体等)
Adobe Launch/GTM等のタグ本体は、コードを直接編集せずsite.config.tsで設定します。Base.astroが、ACDL初期化の後・他のどのスクリプトより前にこの設定を出力するため、タグマネージャーが読み込まれる時点でwindow.adobeDataLayerが必ず存在する状態が保証されます。
新しいイベントを追加する
- パターンA/Bのどちらかを判定する
- パターンBなら該当Blockの
<script>にpushEvent()を追記、パターンAなら中央のブリッジ層に購読処理を追加(パターンAの場合はimportの配線を忘れないこと) - 実際にpushされることをブラウザの開発者ツール等で確認する
動作確認方法
Playwrightなどでpushを検証する場合は、ACDL本体の読み込み前に「関数push」でリスナー登録を仕込みます(ACDLはtypeof item === 'function'のpushをFCTNアイテムとして認識し、ライブラリ初期化時にdataLayerインスタンスを引数に実行します)。window.adobeDataLayer.push自体を独自関数で上書きする方式は、ライブラリ初期化時にその上書きが失われるため使えません。
await page.addInitScript(() => {
window.adobeDataLayer = window.adobeDataLayer || [];
window.__acdlEvents = [];
window.adobeDataLayer.push((dataLayer) => {
dataLayer.addEventListener('adobeDataLayer:change', (event) => {
window.__acdlEvents.push(event);
});
});
});
将来の拡張
CMPの同意状態は、consentといった新しい名前空間を追加でpushするだけで対応できる設計になっています(既存のpage/user/イベント構造の変更は不要)。現時点ではこのスキーマは未設計です。