· HARUBOUYA · analytics · 13 min read

GA4移行後にCVが計上されなくなるGTMタグの直し方

タグは発火しているのにコンバージョンが記録されません。UA時代に書かれたカスタムHTMLタグが静かに失敗する仕組みと、修正前・修正後のコードを並べて直し方を整理します。

purchaseイベントの通過を確認している様子のイラスト。カートのアイコンとチェックマークが並んでいる

外部の計測ツールに送っているコンバージョンが、ある時期からゼロになっていました。GTM のプレビューで見るとタグは「発火」と表示されます。エラーも出ていません。それでも件数は増えません。

原因は、UA(ユニバーサルアナリティクス)時代に書かれたカスタム HTML タグが、GA4 移行後に前提を失っていたことです。同じ構造のタグは多くのサイトに残っているため、症状と直し方を整理しておきます。

なお、以下のコードは実際の実装をもとに、URL・識別子・ベンダー名を伏せた形に書き換えています。

修正前と修正後の違い(先に結論)

観点修正前修正後
値の取り方ページ側のグローバル変数を直接参照dataLayer+GTM変数を経由
キー名UA の actionFields / products / skuGA4 の transaction_id / items / item_id
値がないときタグ内の if で黙って終了トリガー側で発火させません
失敗したときconsole.log に名前だけ出力measurement_error を dataLayer に送信
値の確認方法タグの中を読むしかありませんプレビュー画面で値が見えます
他タグへの依存タグの順序付けに依存1回の push に必要な値をまとめます

以下、修正前のコード、壊れる仕組み、修正後のコードの順に見ていきます。

修正前:問題のあるタグ

<!-- 修正前 -->
<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つずつ説明します。

<!-- 修正後 -->
<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>

あわせて、GTM 側で次の設定を行います。

設定内容
データレイヤー変数DLV - transaction_id / DLV - items / DLV - user_id
トリガーカスタムイベント purchase、条件に {{DLV - transaction_id}} が空でないことを追加
SDK の読み込みタグ内の <script src> ではなく、別タグに分けます

何を変えたか

1. グローバル変数への直接参照をやめる

修正前

// ページ側の変数を、そのまま読んでいる
cvParams['order_id'] = tmp_actionFields['id'];

修正後

// GTM の変数を経由して読む
var orderId = {{DLV - transaction_id}};

GTM の「変数」でデータレイヤー変数を定義しておきます。

変数名の例データレイヤーのキー
DLV - transaction_idecommerce.transaction_id
DLV - itemsecommerce.items
DLV - user_iduser_id

こうすると、値が取れているかどうかを GTM のプレビュー画面で確認できるようになります。タグの中を読まなくても分かる状態にすることが目的です。

2. GA4 の purchase イベントの構造に合わせる

GA4 の purchase では、商品は ecommerce.items の配列で渡されます。キー名も UA 時代とは違います。

修正前(UA の拡張eコマース)修正後(GA4)
actionFields.idtransaction_id
products[].sku / iditems[].item_id
products[].priceitems[].price
products[].quantityitems[].quantity

商品情報を組み立てる部分も書き換えます。

修正前

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);

修正後

var productInfo = items
  .map(function (item) {
    return [item.item_id, item.price, item.quantity].join(':');
  })
  .join(',');

末尾の区切り文字を slice(0, -1) で削る処理が不要になります。商品が0件のときに1文字削ってしまう不具合も、同時に消えます。

3. 失敗を握りつぶさず、検知できる形にする

修正前

} catch (error) {
  console.log(error.name);   // 誰も見ていない
}

修正後

window.dataLayer.push({
  event: 'measurement_error',
  error_tag: 'cv_tag',
  error_reason: 'missing_transaction_id',
});

値が揃わなかったときに measurement_error イベントを dataLayer に流します。これだけで次のことができるようになります。

  • GTM のプレビューで、失敗したことがイベントとして見えます
  • GA4 側でこのイベントをカスタムイベントとして受け、発生件数を監視できます
  • 「CVが0件」ではなく「CVタグが値を取れていない」という形で検知できます

console.log で終わらせません。気づける場所に出すことが要点です。

4. 発火条件はトリガー側に寄せる

修正前

// タグの中で判定している
if (typeof tmp_products !== 'undefined' && tmp_products != null) {
  if (typeof tmp_actionFields !== 'undefined' && tmp_actionFields != null) {
    sendConversion();
  }
}

修正後

判定をタグから外し、トリガーの条件にします。

項目設定
トリガーの種類カスタムイベント
イベント名purchase
条件{{DLV - transaction_id}} が空でありません

タグ内の if で判定すると、発火したのに何も起きない状態が生まれます。トリガー側に寄せると、値がないときはタグ自体が発火しません。GTM のプレビューで「発火しなかったタグ」として一覧に出るため、状態が画面の表示と一致します。

5. タグの順序付けへの依存を減らす

修正前:前処理タグでグローバル変数を組み立て、後続タグがそれを読みます。片方の変更で静かに壊れます。

修正後:サイト側で 1回の dataLayer.push に必要な値をすべて入れます。順序付けが不要になります。

// サイト側(購入完了ページ)
window.dataLayer.push({
  event: 'purchase',
  user_id: 'U12345',
  ecommerce: {
    transaction_id: 'A12345',
    value: 12800,
    currency: 'JPY',
    items: [{ item_id: 'SKU-001', price: 6400, quantity: 2 }],
  },
});

実装側の改修が必要になりますが、長期的にはこれが一番効きます。

直したあとの検証手順

  1. GTM のプレビューモードで購入完了まで進みます
  2. Variables タブで、定義した各変数に値が入っているか確認します
  3. Data Layer タブで ecommerce の中身を確認します
  4. ブラウザの開発者ツールの Network タブで、計測リクエストが実際に送信され、正常なステータスが返っているか確認します
  5. 計測ツール側の管理画面で件数を確認します
  6. GA4 の purchase 件数と突き合わせ、乖離がないか見ます

5と6を飛ばすと、「タグは直ったけれど値が間違っている」状態を見逃します。

移行時のチェックリスト

UA 時代のカスタム HTML タグが残っている場合、次を確認します。

確認項目危ないサイン
ページ側のグローバル変数を直接参照していないかtmp_ などで始まる変数名が出てきます
未定義チェックで黙って終了していないかtypeof ... !== 'undefined' で囲んだだけの if
エラー処理が console.log で終わっていないかcatch の中が1行だけ
タグの順序付けに依存していないか「設定タグ」が指定されています
UA のキー名が残っていないかactionFields、products、sku
送信件数を監視しているか誰も件数を見ていません

まとめ

  • 発火している=送信できている、ではありません。GTM の表示だけで判断しません
  • 修正前はページ側のグローバル変数に依存していました。GA4 移行で前提が変わり、静かに壊れました
  • 修正後は dataLayer と GTM 変数を経由させ、プレビュー画面で値が見える状態にしました
  • 失敗したら measurement_error を流し、検知できる形にしました
  • 条件はタグ内の if ではなくトリガーに寄せました
  • 直したあとは件数の突き合わせまで行います

計測は、壊れても誰も困らない時間が長く続いてから発覚します。壊れたことが分かる仕組みを一緒に入れておきます。

Share:

参考になったら、いいねを押してください。

コメント

    お名前とコメントは、確認後にこのページで公開されます。メールアドレスや電話番号などは書かないでください。

    Back to Blog

    Related Posts

    View All Posts »
    サイズレコメンドのタグをGTMから入れる

    サイズレコメンドのタグをGTMから入れる

    外部のサイズ提案ツールを入れるとき、GTMから3種類のタグを配信します。何を送るか、複数商品を買われたときの書き方、そしてUA時代のeec.purchaseだけでは足りない項目を整理します。