
SNSへの自動投稿からの流入をGA4で確実に数えるには、投稿文に載せるURLにだけUTMパラメータを付け、投稿済みの管理に使う台帳のURLは素のまま残す設計が安全です。Xのアプリ内ブラウザから開かれた訪問はリファラーが落ちてDirectに混ざりやすく、UTMがないとXから来たことを判別できません。この記事では、n8nで動かしているX自動投稿の仕組みに、UTMの自動付与を組み込んだときの設計と実装を紹介します。
背景: Xからの流入がDirectに埋もれる
当社では、AI情報メディア「AI-PLEX」の新着記事をRSSで検知し、紹介文を生成してXへ自動投稿する仕組みをn8nで運用しています。投稿の効果を見て、文面やタグの付け方をチューニングするのが目的です。
ところが、Xのアプリ内ブラウザで記事を開かれるとリファラーが落ち、GA4上では参照元のない「Direct」に混ざってしまいます。これでは、どの投稿がどれだけ読まれたのかという、チューニングの根拠になるデータが取れません。
そこで、投稿するURLにUTMパラメータを自動で付けることにしました。
付与する内容: 値はConfigに集約する
付けるのは次の4つです(各パラメータの意味は、Googleアナリティクスのヘルプ「URL生成ツール: カスタムURLでキャンペーンデータを収集する」にまとまっています)。
utm_source: 投稿先のプラットフォーム名(現在はx。将来Instagramに対応したときに自動で切り替わるよう、設定値から取る)utm_medium:socialutm_campaign:auto_post(自動投稿であることを示す)utm_content: 投稿フォーマットの版数(例:v2_3)
campaignとcontentの値は、ワークフロー先頭のConfigノードに集約しました。値を変えたいときに、コードを触らず設定だけ直せるようにするためです。GA4側ではutm_campaign=auto_postで絞り込めば、自動投稿経由の訪問だけを見られます。
設計の肝: 台帳のURLは素のまま残す
この仕組みでは、投稿済みの記事を記録する台帳と、投稿待ちのキューを持っており、同じ記事を二重に投稿しないよう、URLで重複を判定しています。
ここで台帳やキューに保存するURLまでUTM付きにすると、フォーマットの版数(utm_content)を上げた時点で、同じ記事でもURLの文字列が変わります。すると重複判定が「別の記事」とみなしてしまい、二重投稿につながりかねません。
そこで、台帳・キューのURLは素のまま保存し、投稿文を組み立てる瞬間にだけUTMを付けることにしました。実際のコード(n8nのCodeノード)は次のとおりです。
// 投稿文に載せるURLだけUTM付き(台帳・キューのlinkは素のまま=重複判定を壊さない)。Xは長さに関わらずURL=23字換算
const utmLink = j.link + (String(j.link).includes('?') ? '&' : '?') + 'utm_source=' + (j.platform || 'x') + '&utm_medium=social&utm_campaign=' + (j.utmCampaign || 'auto_post') + (j.utmContent ? '&utm_content=' + j.utmContent : '');
const build = (sum, gd, tags) => (sum ? sum + (tags ? '\n' + tags : '') + '\n\n' + (gd ? gd + '\n' : '') : j.title + (tags ? '\n' + tags : '') + '\n\n') + utmLink;
j.linkは台帳と同じ素のURLで、utmLinkは投稿文の中でしか使いません。元のURLにすでに?が含まれている場合は&でつなぐようにしています。
文字数のコストはゼロ
UTMを付けるとURLは長くなりますが、XではURLは長さに関わらず23字として数えられます(X公式ドキュメントの文字数カウント仕様)。投稿文の文字数を検証する関数も、この仕様に合わせてURLを一律23字で計算しています。
const wl = (t) => {
const urls = t.match(/https?:\/\/\S+/g) || [];
let n = 23 * urls.length;
for (const ch of t.replace(/https?:\/\/\S+/g, '')) n += w(ch.codePointAt(0));
return n;
};
そのため、UTMを付けても本文に使える文字数は減りません。テストでは、通常の投稿が231/280、書き直しを経た投稿が260/280で、どちらも上限内に収まることを確認してから公開しました。
ハマりポイント
投稿文を組み立てる場所が2か所あった
当時のワークフローには、通常の投稿文を組み立てるルートと、AIの校閲で差し戻された投稿文を書き直すルートの2つがあり、それぞれに組み立て用の関数がありました。片方だけにUTMを入れると、書き直しを経た投稿だけUTMなしで出てしまいます。両方の関数に同じ処理を入れ、テストでも両ルートで付与されることを確認しました。
フォーマットを変えたら版数も上げる
utm_contentは投稿フォーマットの版数なので、フォーマットを変えるたびに更新しないと、GA4で新旧の比較ができなくなります。実際、その後の改修でv2_5、v2_6、v2_7と上げてきました。更新漏れを防ぐため、運用ルールを書いたドキュメントに「フォーマット変更時はConfigのutmContentも更新すること」と明記しています。
まとめ
- Xのアプリ内ブラウザからの訪問はリファラーが落ちてDirectに混ざるため、自動投稿のURLにはUTMを付けて計測します。
- 重複判定に使う台帳・キューのURLは素のまま残し、UTMは投稿文を組み立てるときにだけ付けます。版数を上げても二重投稿が起きません。
- XではURLが一律23字換算なので、UTMを付けても文字数のコストはかかりません。
- campaignやcontentの値はConfigに集約し、フォーマットを変えたら版数も上げます。
こうした業務自動化の仕組みづくりは、AI自動化代行で構築から運用まで承ります。ご相談はお問い合わせフォームからどうぞ。
← TECH-BLOG一覧へ