· ハルボウヤ · analytics · 10 min read
GA4移行後にCVが計上されなくなるGTMタグの直し方
タグは発火しているのにコンバージョンが記録されない。UA時代に書かれたカスタムHTMLタグが静かに失敗する仕組みと、その直し方を整理する。

外部の計測ツールに送っているコンバージョンが、ある時期からゼロになっていた。GTM のプレビューで見るとタグは「発火」と表示される。エラーも出ていない。それでも件数は増えない。
原因は、UA(ユニバーサルアナリティクス)時代に書かれたカスタム HTML タグが、GA4 移行後に前提を失っていたこと。同じ構造のタグは多くのサイトに残っているため、症状と直し方を整理しておく。
なお、以下のコードは実際の実装をもとに、URL・識別子・ベンダー名を伏せた形に書き換えている。
問題のあるタグの形
<script src="(計測ベンダーが配布するSDKのURL)"></script>
<script>
var cvParams = {};
function sendConversion() {
try {
cvParams['order_id'] = tmp_actionFields['id'];
if (customerId != null && customerId.length > 0) {
cvParams['user_id'] = customerId;
}
if (visitorId != null && visitorId.length > 0) {
cvParams['session_id'] = visitorId;
}
var productInfo = '';
for (var i = 0; i < tmp_products.length; i++) {
var row = tmp_products[i];
productInfo += (row['sku'] || row['id'] || '') + ':';
productInfo += row['price'] + ':';
productInfo += row['quantity'] + ',';
}
cvParams['product_info'] = productInfo.slice(0, -1);
vendorAnalytics.cv.sendTrackingRequest('(テナントID)', cvParams);
} catch (error) {
console.log(error.name);
}
}
if (typeof tmp_products !== 'undefined' && tmp_products != null) {
if (typeof tmp_actionFields !== 'undefined' && tmp_actionFields != null) {
sendConversion();
}
}
</script>tmp_products、tmp_actionFields、customerId、visitorId は、ページ側が定義したグローバル変数。変数名はサイトによって異なるが、UA の拡張eコマース実装ではこの形がよく使われていた。
なぜ気づけないのか
このタグが厄介なのは、壊れても何も言わない点にある。理由が3つ重なっている。
1. 未定義チェックが「静かに何もしない」
最後の if で、グローバル変数が未定義なら関数を呼ばずに終わる。GA4 移行でページ側の dataLayer 設計が変わり、これらの変数が定義されなくなると、タグはエラーも出さず、送信もせずに終了する。
2. GTM のプレビューでは「発火」と表示される
GTM が判定しているのは、トリガー条件を満たしてタグが実行されたかどうか。タグの中で処理が握りつぶされていても、画面上は正常に見える。発火=成功ではない。
3. エラーが console.log に出るだけ
catch の中が console.log(error.name) のみ。ブラウザのコンソールを開いている人がいなければ、誰も気づかない。しかも出るのは例外の名前だけで、原因の特定に使えない。
さらに、このタグは「タグの順序付け」で別の前処理タグに依存している。前処理タグ側の変更でも同じように静かに壊れる。
改善の方針
1. グローバル変数への直接参照をやめる
ページ側のグローバル変数を JavaScript から直に触る構造を、dataLayer と GTM 変数を経由する形に変える。
GTM の「変数」でデータレイヤー変数を定義する。
| 変数名の例 | データレイヤーのキー |
|---|---|
| DLV - transaction_id | ecommerce.transaction_id |
| DLV - items | ecommerce.items |
| DLV - user_id | user_id |
カスタム HTML の中では {{DLV - transaction_id}} の形で参照する。こうすると、値が取れているかどうかを GTM のプレビュー画面で確認できるようになる。タグの中を読まなくても分かる状態にすることが目的。
2. GA4 の purchase イベントの構造に合わせる
GA4 の purchase では、商品は ecommerce.items の配列で渡される。キー名も UA 時代とは違う。
| UA の拡張eコマース | GA4 |
|---|---|
actionFields.id | transaction_id |
products[].sku / id | items[].item_id |
products[].price | items[].price |
products[].quantity | items[].quantity |
移行後のコードはこうなる。
<script>
(function () {
var orderId = {{DLV - transaction_id}};
var items = {{DLV - items}};
if (!orderId || !items || !items.length) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'measurement_error',
error_tag: 'cv_tag',
error_reason: !orderId ? 'missing_transaction_id' : 'missing_items',
});
return;
}
var productInfo = items
.map(function (item) {
return [item.item_id, item.price, item.quantity].join(':');
})
.join(',');
vendorAnalytics.cv.sendTrackingRequest('(テナントID)', {
order_id: orderId,
user_id: {{DLV - user_id}},
product_info: productInfo,
});
})();
</script>3. 失敗を握りつぶさず、検知できる形にする
変更点は、値が揃わなかったときに measurement_error イベントを dataLayer に流すこと。
これだけで次のことができるようになる。
- GTM のプレビューで、失敗したことがイベントとして見える
- GA4 側でこのイベントをカスタムイベントとして受け、発生件数を監視できる
- 「CVが0件」ではなく「CVタグが値を取れていない」という形で検知できる
console.log で終わらせない。気づける場所に出すことが要点。
4. 発火条件はトリガー側に寄せる
「変数が存在するか」の判定をタグ内の if に書くと、発火したのに何も起きない状態が生まれる。これをトリガーの条件に移す。
- トリガー:カスタムイベント
purchase - 条件:
{{DLV - transaction_id}}が空でない
こうすると、値がないときはタグ自体が発火しない。GTM のプレビューで「発火しなかったタグ」として一覧に出るため、状態が正しく見える。
5. タグの順序付けへの依存を減らす
前処理タグでグローバル変数を組み立て、後続タグがそれを読む構成は、片方の変更で静かに壊れる。
サイト側で 1回の dataLayer.push に必要な値をすべて入れる形にできれば、順序付けは不要になる。実装側の改修が必要になるが、長期的にはこれが一番効く。
直したあとの検証手順
- GTM のプレビューモードで購入完了まで進む
- Variables タブで、定義した各変数に値が入っているか確認する
- Data Layer タブで
ecommerceの中身を確認する - ブラウザの開発者ツールの Network タブで、計測リクエストが実際に送信され、正常なステータスが返っているか確認する
- 計測ツール側の管理画面で件数を確認する
- GA4 の
purchase件数と突き合わせ、乖離がないか見る
5と6を飛ばすと、「タグは直ったが値が間違っている」状態を見逃す。
移行時のチェックリスト
UA 時代のカスタム HTML タグが残っている場合、次を確認する。
| 確認項目 | 危ないサイン |
|---|---|
| ページ側のグローバル変数を直接参照していないか | tmp_ などで始まる変数名が出てくる |
| 未定義チェックで黙って終了していないか | typeof ... !== 'undefined' で囲んだだけの if |
エラー処理が console.log で終わっていないか | catch の中が1行だけ |
| タグの順序付けに依存していないか | 「設定タグ」が指定されている |
| UA のキー名が残っていないか | actionFields、products、sku |
| 送信件数を監視しているか | 誰も件数を見ていない |
まとめ
- 発火している=送信できている、ではない。GTM の表示だけで判断しない
- UA 時代のグローバル変数依存は、GA4 移行で静かに壊れる
- dataLayer と GTM 変数を経由させ、プレビュー画面で値が見える状態にする
- 失敗したら
measurement_errorを流し、検知できる形にする - 条件はタグ内の
ifではなくトリガーに寄せる - 直したあとは件数の突き合わせまでやる
計測は、壊れても誰も困らない時間が長く続いてから発覚する。壊れたことが分かる仕組みを一緒に入れておく。



