2026.10.05

SNS自動投稿のURLにだけUTMを付け、台帳のURLは素のまま残す(n8nでX流入をGA4で数える設計)

SNS自動投稿のURLにだけUTMを付け、台帳のURLは素のまま残す(n8nでX流入をGA4で数える設計)

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でキャンペーンデータを収集する」にまとまっています)。

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も更新すること」と明記しています。

まとめ

こうした業務自動化の仕組みづくりは、AI自動化代行で構築から運用まで承ります。ご相談はお問い合わせフォームからどうぞ。

← TECH-BLOG一覧へ