広告計測について

広告効果の計測は時間がかかりサイト全体に影響があるので、
安易に考えないでほしい内容です。
ここに最低限の対応を記載してみます。
今回の目的
「広告クリック → コンバージョン」までのデータ取得ができる状態にすること
広告効果を測定するための「パラメータ付きURL」
1. そもそもパラメータ付きURLとは?
広告やSNS投稿などのURLの末尾に「utm_campaign」などの計測用パラメータを追加することで、Google Analytics(GA)などのツールで「どの広告から来たアクセスか?」を分析できるようになります。
パラメータ付きURLの作成手順(Campaign URL Builder 使用)
〇 使用ツール
Googleの公式ツール「Campaign URL Builder」
※GA4にも対応済み
📌 入力項目(主要パラメータ)
パラメータ | 説明 | 例 |
---|---|---|
Website URL(必須) | 訪問先のページURL | https://example.com/landing |
Campaign Source(必須) | トラフィックの出所 | google, facebook, newsletter など |
Campaign Medium(必須) | 媒体 | cpc(広告), email, banner など |
Campaign Name | キャンペーン名 | summer_sale, 2025_launch など |
Campaign Term | キーワード(任意) | 広告のキーワード(PPC広告時) |
Campaign Content | 広告の内容(任意) | A/Bテスト用のバナー名など |
URLを使った広告効果の確認方法(Google Analytics で)
- 上記URLを使って広告を出す(例:Google広告に設定)。
- GA4にアクセス。
- 「集客 > トラフィック獲得」などのレポートで
utm_source
→ どこから来たかutm_medium
→ どの媒体かutm_campaign
→ どのキャンペーンか
などが確認できます。
運用のポイント
- 一貫性:utmの値(sourceやcampaign名など)は統一ルールを作ると後の分析がしやすくなります。
- 記録:チームで共有するスプレッドシートなどに発行したURLとパラメータ内容を記録しておきましょう。
- 文字数:あまり長くならないように各パラメータは15文字程度に抑える
- 短縮は使わない:URLが長くなる場合でも、URL短縮サービス(bitlyなど)は推奨しません。理由は、後続ページでのUTMパラメータの引き継ぎや計測が煩雑になり、GA上での分析精度が下がる可能性があるためです。
コンバージョン計測
GoogleのUTMパラメータを使えば「どの広告から流入したか」はわかりますが、コンバージョン(例:資料請求、購入、問い合わせ)まで正確にひもづけるには追加の工夫が必要です。
下記のスクリプトの役割です
このスクリプトを使うことで:
GAやCRMでユーザーの行動分析や絞り込みがしやすくなる
広告→流入→フォーム送信→コンバージョンまで、一貫してトラッキング可能
「広告の費用対効果(ROAS)」を正確に測定
計測タグを引き継ぐJS
/**
* utm_url_val_add_v9.js
* ©2025 sakaida.jp WEBディレクターって なんですの?
* - UTMパラメータをセッションに保持し、<a>, <form>, <button>, <input> に付加
* - form要素ではactionを書き換えず、hidden inputで追加
* - POST送信後の画面ではhiddenからUTMを読み出してURLに追加(アンカー含め保持)
*/
(function () {
document.addEventListener('DOMContentLoaded', function () {
const PARAM_KEYS = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_term',
'utm_content'
];
const STORAGE_KEY = '__myUtmParams__';
// 現在のURLの検索パラメータを取得
const url = new URL(window.location.href);
const rawParams = url.searchParams;
let safeParams = {};
// 危険な値でないかチェック
function isSafe(val) {
return typeof val === 'string' && !/[<>'"(){}]|script|on\w+=|javascript:|data:/i.test(val);
}
// 不要な文字を除去
function sanitize(val) {
return val.replace(/[^\w\-]/g, '');
}
// UTMを抽出・サニタイズ
PARAM_KEYS.forEach(k => {
const val = rawParams.get(k);
if (val && isSafe(val)) safeParams[k] = sanitize(val);
});
// セッションストレージに保存(既に保存済みなら読み出し)
const stored = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '{}');
if (Object.keys(stored).length === 0 && Object.keys(safeParams).length > 0) {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(safeParams));
} else if (Object.keys(stored).length > 0) {
safeParams = stored;
}
// 要素にUTMを追加
function addParamsToElement(element, type) {
try {
let targetUrlString = '';
if (type === 'a' && element.hasAttribute('href')) {
targetUrlString = element.getAttribute('href');
} else if (type === 'form' && element.hasAttribute('action')) {
targetUrlString = element.getAttribute('action');
} else if ((type === 'button' || type === 'input') && element.hasAttribute('formaction')) {
targetUrlString = element.getAttribute('formaction');
} else {
return;
}
let isHashOnly = false;
// href="#xxx" のような場合
if (type === 'a' && targetUrlString.startsWith('#')) {
targetUrlString = location.pathname + location.search + targetUrlString;
isHashOnly = true;
}
const targetUrl = new URL(targetUrlString, location.origin);
const originalHash = targetUrl.hash;
let modified = false;
Object.keys(safeParams).forEach(k => {
targetUrl.searchParams.set(k, safeParams[k]);
modified = true;
});
if (!modified) return;
if (type === 'a') {
targetUrl.hash = originalHash;
element.href = isHashOnly
? targetUrl.pathname + targetUrl.search + originalHash
: targetUrl.href;
} else if (type === 'form') {
Object.keys(safeParams).forEach(k => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = k;
input.value = safeParams[k];
element.appendChild(input);
});
if (element.method.toLowerCase() === 'get') {
element.method = 'post';
}
} else if (type === 'button' || type === 'input') {
const targetForm = element.form;
if (targetForm) {
const actionUrl = new URL(element.getAttribute('formaction'), location.origin);
const originalHash = actionUrl.hash;
Object.keys(safeParams).forEach(k => {
actionUrl.searchParams.set(k, safeParams[k]);
});
actionUrl.hash = originalHash;
element.setAttribute('formaction', actionUrl.href);
Object.keys(safeParams).forEach(k => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = k;
input.value = safeParams[k];
targetForm.appendChild(input);
});
if (targetForm.method.toLowerCase() === 'get') {
targetForm.method = 'post';
}
} else {
element.setAttribute('formaction', targetUrl.href);
}
}
} catch (e) {
// 無効なURLなどのエラーを無視
}
}
// 初期ロード時にすべての対象要素に対してUTM付加
function processAllElements() {
Array.from(document.getElementsByTagName('a')).forEach(el => addParamsToElement(el, 'a'));
Array.from(document.getElementsByTagName('form')).forEach(el => addParamsToElement(el, 'form'));
Array.from(document.querySelectorAll('button[formaction]')).forEach(el => addParamsToElement(el, 'button'));
Array.from(document.querySelectorAll('input[type="submit"][formaction]')).forEach(el => addParamsToElement(el, 'input'));
}
// 動的に追加された要素にも対応
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
if (node.tagName === 'A') addParamsToElement(node, 'a');
if (node.tagName === 'FORM') addParamsToElement(node, 'form');
if (node.tagName === 'BUTTON' && node.hasAttribute('formaction')) addParamsToElement(node, 'button');
if (node.tagName === 'INPUT' && node.type === 'submit' && node.hasAttribute('formaction')) addParamsToElement(node, 'input');
if (node.querySelectorAll) {
node.querySelectorAll('a, form, button[formaction], input[type="submit"][formaction]')
.forEach(el => {
const tag = el.tagName;
if (tag === 'A') addParamsToElement(el, 'a');
if (tag === 'FORM') addParamsToElement(el, 'form');
if (tag === 'BUTTON') addParamsToElement(el, 'button');
if (tag === 'INPUT') addParamsToElement(el, 'input');
});
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
processAllElements();
// --- POST送信後のページで、hiddenからUTMを復元しURLに追加 ---
const currentUrl = new URL(window.location.href);
const utmFromHidden = {};
PARAM_KEYS.forEach(k => {
const el = document.querySelector(`input[name="${k}"]`);
if (el && el.value) utmFromHidden[k] = el.value;
});
if (Object.keys(utmFromHidden).length > 0) {
Object.keys(utmFromHidden).forEach(k => {
currentUrl.searchParams.set(k, utmFromHidden[k]);
});
// アンカーも含めて正しく再構成
const newUrl = currentUrl.origin + currentUrl.pathname + '?' + currentUrl.searchParams.toString() + currentUrl.hash;
history.replaceState(null, '', newUrl);
}
});
})();
実装方法
- 上記スクリプト(
utm_url_val_add_v9.js
)を任意のファイルとして保存 - HTMLの
<head>
などに下記のように読み込み
<script src="/path/to/utm_url_val_add_v9.js" defer></script>
セキュリティ対応も万全
- JavaScript内でパラメータの値に対して XSS対策(文字フィルタリング) がされている
script
,onload
,data:
などの危険なパターンを排除sanitize()
により不要な文字も除去
HTML側での設定:コンバージョンデータを「わかりやすく」するために
GAでのコンバージョン分析を分かりやすくするために、HTML側で以下の対応が重要です:
A. ページの <title>
を適切に設定
例(サンクスページ):
<title>資料請求完了|〇〇株式会社</title>
GAでは page_title
として収集されるので、何のページでのコンバージョンかがレポート上明確になります。
B. <h1>
も明確に
ユーザーにも検索エンジンにも意味が伝わるように:
<h1>資料請求ありがとうございました</h1>
C. メタタグで補足情報
例:
<meta name="robots" content="noindex, nofollow">
<meta name="description" content="〇〇株式会社への資料請求が完了しました。">
※「サンクスページ」をインデックスさせたくない場合に便利。
D. hiddenで送信されたUTMを確認用に出力(開発・検証用)
フォーム送信後に送られたパラメータを、GET
や hidden で受け取って可視化すると確認が簡単です:
<script>
const params = new URLSearchParams(location.search);
console.log("UTMパラメータ:", Object.fromEntries(params.entries()));
</script>
フォーム送信先で受け取れるようにする
フォーム送信時(POST/GET)に、UTM情報が hidden フィールドとして送られていれば、バックエンドやCRM側でも「どの広告経由か?」が記録可能です。
<!-- 例:受信側でPHPなどで記録可能 -->
<input type="hidden" name="utm_source" value="google">
<input type="hidden" name="utm_campaign" value="spring_sale">
今回までの成果(準備段階)
内容 | 状態 |
---|---|
UTMの取得 | 取得してセッションに保存 |
ページ遷移での保持 | 自動でリンクやフォームに付加 |
フォーム送信 | hiddenによりUTMも送信される |
HTML側での視認性 | title/h1/metaでコンバージョン明示 |
どんな内容で何を計測したいか?
どんなレポートが欲しいかによって準備内容が異なります。
今回はここまで・・・・ではまた