イベント設計とタクソノミー ― 後で分析できる計測にする
命名規約・パラメータ設計・データレイヤーといったイベント設計の原則を扱います。計測の段階で設計を誤ると下流の分析が破綻するため、一貫した規約とデータレイヤーで「あとから集計・結合できる」計測をどう作るかを、架空D2CのSOLNAを例に整理します。
by Akisame
「とりあえずイベントを取っておこう」で実装した計測は、後から使おうとすると高い確率で使えません。原因は分析力ではなく、命名・パラメータ・データレイヤーが無設計のまま放置され、集計も結合もできない断片になっているからです。この記事では、あとから集計・結合できる計測にするためのイベント設計の原則を、過去の受託案件をもとに設計した架空D2C「SOLNA」を題材に、データレイヤーの実装の最小構成とBigQueryでの監査クエリまで含めて整理します。
この計測が下流の何を可能にするか(真のCAC/LTV・接合率という適用上限)の判断軸は記述・予測編に置いています。本記事はその手前、「そもそも後で使えるデータをどう生むか」を扱います。
命名規約 ― 一貫性が分析可能性を決める
イベント名は集計の単位です。ここが揺れると、同じ行動が複数の名前に分裂し、ファネルもCVRも静かに割れます。たとえば購入完了を、現場や実装担当ごとに purchase / buy / order_complete と付けてしまうと、本来一つの成果が三つに散り、それぞれが過小に見えます。誰も嘘をついていないのに、数字が事実から乖離する典型です。
原則は三つだけです。第一に snake_case で固定する。第二に「名詞_動詞」など語順のパターンを一つ選び、連載・サイト全体で貫く。第三に、標準があるなら自作しないことです。GA4の推奨イベント(view_item / add_to_cart / begin_checkout / purchase / sign_up / login)はEC計測の共通のことばなので、これを独自名で上書きしないほうが、媒体連携でも後続ツールでも素直に動きます。自前の命名が必要なのは、標準に存在しない事業固有イベント(定期購入の開始・解約など)だけです。
ここで一言添えます。「立派な命名規約を作る」ことより、「既存の標準に寄せ、足りない分だけ最小限に拡張する」ほうが、長期の保守コストは小さくなります。規約の独自性は資産ではなく負債になりやすい。覚えること・例外・引き継ぎコストが増えるからです。
パラメータ設計 ― 何を一緒に取るか
イベント名が「何が起きたか」だとすれば、パラメータは「その文脈」です。後で切り分けたい軸は、イベントが発火する瞬間に一緒に取らないと、原理的に復元できません。だからパラメータ設計の起点は「下流でどう分けたいか」から逆算することになります。
SOLNAでは、購入を「初回 / リピート / 定期」で分けたい。ならば purchase に必ず order_type('first' / 'repeat' / 'subscription')を載せます。これが無いと、初回獲得コストとリピートを混ぜたまま集計してしまい、真のCAC/LTV(記述・予測編)が計測だけでは出せません。後からDB照合で補うことはできますが、その照合自体が結合キーに依存するので、結局この後の設計に跳ね返ります。
値の規約も同時に決めます。value は税抜・税込のどちらか一方に固定し、currency を必ず添える。ここが実装ごとにぶれると、ROASや客単価が現場をまたいだ瞬間に狂います。金額は「数字が入っているか」ではなく「定義が一意か」で品質が決まります。
そして最重要が、結合キーをパラメータとして必ず載せることです。行動側の ga_client_id と、会員側の customer_id。purchase や sign_up のように本人が確定する瞬間に両方を取れれば、広告接触 → 行動 → 成果を一本に辿れる素地ができます。これが第5回で扱う接合率の出発点になります。
データレイヤー ― 計測とサイトを疎結合にする
タグ計測をサイトのDOMやJSに直接結びつけると、二つの方向で壊れます。サイトを改修するたびに計測が落ち、計測ツールを足すたびにサイトを触る羽目になります。どちらも、計測とサイトが密結合(片方を変えると他方も変えざるを得ない状態)だから起きます。
データレイヤーは、その間に挟む中立的な層です。サイト側は「意味のあるイベント」を共通の構造(dataLayer)に流すだけにし、GA4・広告タグ・将来のサーバーサイド計測(第3回)は、その構造を読む側に回ります。こうしておくと、(a) サイト改修に計測が巻き込まれにくくなり、(b) 計測ツールの差し替え・追加を、サイト本体を触らずに行えます。次回扱うサーバーサイド計測も、この疎結合(一方を変えても他方に波及しない状態)があって初めて移行が楽になります。
設計の土台として、SOLNAのイベント定義を先に固定します(抜粋)。
SOLNAのイベント定義(抜粋)
| イベント | 区分 | 発火タイミング | 主要パラメータ | 結合キー |
|---|---|---|---|---|
view_item | 閲覧 | 商品詳細の表示 | item_id, item_name, item_category, price | ga_client_id |
add_to_cart | 閲覧 | カート追加 | item_id, quantity, value, currency | ga_client_id |
begin_checkout | 注文 | 決済開始 | value, currency, items[] | ga_client_id |
purchase | 注文 | 購入完了 | transaction_id, value, currency, order_type, items[] | ga_client_id + customer_id |
sign_up | 会員 | 会員登録完了 | method, membership_tier | ga_client_id + customer_id |
subscription_start | 会員 | 定期購入の開始 | subscription_id, plan, value, currency | customer_id |
subscription_cancel | 会員 | 定期購入の解約 | subscription_id, plan, n_cycles | customer_id |
サイト側が purchase を流す最小構成は次の通りです。タグツール固有の設定はここでは扱わず、サイトが何を流すかだけに集中します。
// 購入完了時、サイトは「意味のあるイベント」をdataLayerへ流すだけにする。
// GA4・広告・将来のサーバーサイドは、この構造を読む側に回る(=疎結合)。
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "purchase", // 命名規約: snake_case・標準名を踏襲
// 結合キー(本人が確定するこの瞬間に両方を取る)
customer_id: "c_8f2a1d", // 会員ID(成果側の背骨)
ga_client_id: "GA1.1.1234567890", // 行動側(媒体・流入と紐づく)
// 文脈パラメータ(下流で分けたい軸を先に確保する)
transaction_id: "ord_24193",
order_type: "subscription", // first | repeat | subscription
value: 6800, // 税抜で統一(規約として固定)
currency: "JPY",
items: [{ item_id: "sku_serum_30", quantity: 1, price: 6800 }],
});命名規約とパラメータ規約は、ドキュメントに書くだけだと必ず形骸化します。実装者が善意で名前を足し、いつの間にか逸脱が積もるからです。これを防ぐには、タクソノミーを「取り決め(data contract)」としてコード化し、レビュー対象にするのが現実的です。
# event_taxonomy.yml ― 計測の「取り決め」。PRレビューと自動チェックの対象にする。
purchase:
required_params: [transaction_id, value, currency, order_type]
join_keys: [ga_client_id, customer_id] # 両方必須
enum:
order_type: [first, repeat, subscription]
currency: [JPY]
sign_up:
required_params: [method, membership_tier]
join_keys: [ga_client_id, customer_id]
subscription_cancel:
required_params: [subscription_id, plan, n_cycles]
join_keys: [customer_id]取り決めとして版管理しておけば、「いつ・誰が・何を変えたか」が履歴に残り、後述する計測方式の不連続も追跡できます。
RSC 時代の実装 ― 属性駆動+イベント委譲
疎結合の原則は、モダンフロントエンドでは実装の形も変えます。React Server Components(Next.js App Router)のサイトでCTAクリック計測を足すとき、リンクごとに onClick を付けるためにコンポーネントを client 化するのはアンチパターンです。計測のためだけにクライアントJSのバンドルが増え、サーバーコンポーネントの利点(軽さ・データ取得の単純さ)を手放すことになります。計測都合がサイト実装を侵食している時点で、本節の密結合の再来です。
代わりに、マークアップには data-track 属性を宣言的に置くだけにし、レイアウト側に置いた単一のリスナーが拾って dataLayer に流します。
// analytics-listener.tsx ― ルートレイアウトに1つだけ置く client component
"use client";
import { useEffect } from "react";
export function AnalyticsListener() {
useEffect(() => {
const onClick = (e: MouseEvent) => {
// 修飾キー・主ボタン以外・処理済みイベントは素通し(リンクの開き方を変えない)
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
const el = (e.target as HTMLElement | null)?.closest<HTMLElement>("[data-track]");
if (!el) return;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: el.dataset.track, // 例: "cta_click"
track_label: el.dataset.trackLabel, // 例: "hero"
}); // GTMを使わない構成なら、この1行を gtag("event", ...) に差し替えるだけ
};
document.addEventListener("click", onClick);
return () => document.removeEventListener("click", onClick);
}, []);
return null;
}計測される側は、サーバーコンポーネントのまま属性を1つ置くだけです。
{/* client 化しない。計測したい意図だけをマークアップに宣言する */}
<a href="/contact" data-track="cta_click" data-track-label="hero">相談する</a>これは本節の疎結合原則そのものです。マークアップは「何を計測するか」だけを宣言し、「どう送るか」はリスナーに集約する。送り先を GA4 直・GTM・サーバーサイドへ差し替えても、ページ側のコードは1文字も変わりません。実装上の注意は2点だけ——closest() で属性を探索しているのでリンク内部の子要素をクリックしても拾えること、そして修飾キー・主ボタン以外を素通ししているのでリンク本来の挙動(新規タブで開く等)やアクセシビリティに影響を与えないこと(DOMも一切変えません)。
設計を誤ると下流で何が壊れるか
ここまでの設計を怠ると、壊れる場所は計測そのものではなく、いつも下流です。具体的に三つ挙げます。
第一に、表記ゆれはイベントを分裂させ、CVRやファネルを過小・重複させます。これはdbt連載(整える)で後追い修復することになりますが、修復しても「施策前後の差はばらつきか効果か」という判定(統計の土台)まで歪んだままになりがちです。汚れたまま整えても、整っただけで正しくはなりません。
第二に、必須パラメータの欠損は分析の解像度を奪います。order_type が無ければ、初回・リピート・定期を計測だけで分解できず、真のCAC/LTVが出せません。下流の予測・因果以前に、記述(事業を正しく読む段階)が土台から成立しなくなります。
第三に、結合キーの未確保が、もっとも静かで致命的です。purchase に customer_id が付いていなければ、広告接触から成果までを一人として辿れず、接合率が頭打ちになります。接合率は第5回のID統合で測り・引き上げますが、発火時に取り損ねたキーは後付けできません。つまり「接合率=下流手法の上限」は、ほぼこのイベント設計の瞬間に決まってしまうのです。
監査は感覚ではなく数字で回します。GA4のBigQuery Exportを題材に、タクソノミーの健全性を三点だけ機械的に測る最小構成を置きます。
-- タクソノミー健全性の最小監査:
-- 1) 表記ゆれ検出 2) 必須パラメータ欠損率 3) 結合キー付与率(接合率の前段)
WITH ev AS (
SELECT
event_name,
user_id AS customer_id, -- GA4 user_id に customer_id を流す設計
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'order_type') AS order_type
FROM `solna.analytics_XXXXXX.events_*`
WHERE _TABLE_SUFFIX BETWEEN '20260501' AND '20260531'
)
SELECT
-- 1) purchase の表記ゆれ候補(標準名以外が混ざっていないか)
COUNTIF(event_name IN ('buy', 'order_complete', 'purchase_done')) AS misnamed_purchase,
-- 2) purchase に order_type が無い割合(真CAC/LTVが出せなくなる)
SAFE_DIVIDE(
COUNTIF(event_name = 'purchase' AND order_type IS NULL),
COUNTIF(event_name = 'purchase')) AS order_type_missing_rate,
-- 3) purchase に customer_id が付いた割合(成果まで辿れる素地=接合率の前段)
SAFE_DIVIDE(
COUNTIF(event_name = 'purchase' AND customer_id IS NOT NULL),
COUNTIF(event_name = 'purchase')) AS purchase_join_key_rate
FROM ev;この三つの数字を定期的に出し続けることが、計測の品質保証(第1回の横串)の具体化です。壊れは一度直せば終わりではなく、実装が変わるたびに再発します。
章末チェックリスト
- 命名規約があり、標準イベントを独自名で上書きしていないか
- 下流で分けたい軸(
order_typeなど)をイベント発火時に取れているか - 結合キー(
customer_id/ga_client_id)を計測時点で確保しているか - 値の規約(税抜・税込/通貨)が一意に決まっているか
- タクソノミーを「取り決め」として版管理し、逸脱を検知できるか
- データレイヤーの役割(サイトと計測の疎結合)を説明できるか
- クリック計測のためにコンポーネントを client 化していないか(
data-track属性+イベント委譲で足りないか)
ここまでが「後で使える計測をどう生むか」という実装の話です。生んだ計測が、自社で実際にどの分析を可能にするのか(真のCAC/LTV、そして接合率という適用上限)の判断は、記述・予測編へ。
次回はサーバーサイド計測(sGTM)がなぜ必要かを見ます。本記事で作ったデータレイヤーの疎結合が、その移行の前提になります。