「このリンク、誰も踏んでいない」とずっと思っていました。実際には、外部リンク73本にクリック計測のコードが1行も入っていなかっただけでした。
そこから自分の計測を全部洗い直したら、「0」の読み違いが6種類出てきました。6件とも、GA4 と Search Console が表示していた数字自体は正しいです。壊れていたのは読み方のほうでした。
しかもそのうち2件は、すでに施策の No-Go を確定させる直前まで行っていました。エラーは1件も出ていません。0 はエラーにならないからです。
この記事は、その6件と、最後に見つかった「本物の 0」1件の実測記録です。
前提
- 対象:個人で運営している静的サイト数本(資格学習サイト takken.mainichi-lab.com と、方言の辞書サイト hogen.mainichi-lab.com ほか)
- 計測:GA4 3プロパティ + Search Console(ドメインプロパティ)
- 期間:2026年8月〜9月2日
- 収益:現時点で実質ゼロ。だから「効いた/効かなかった」の判定を間違えると、次に何を作るかを丸ごと間違える
0-1. 「測る仕掛けが無い」0
一番多かったのがこれでした。
外部リンクのクリック数がずっと 0 だったので「誰も踏んでいない」と思っていました。実際は onclick も GA4 イベントも一切実装されていなかっただけです。
リンクの張り先ごとに grep で数えました。
grep -roE 'href="https?://[^"]+"' . --include="*.html" \
| grep -oE 'https?://[^/"]+' | sort | uniq -c | sort -rn
| 張り先 | 本数 | クリック計測 |
|---|---|---|
| 記事プラットフォーム | 42 | なし |
| ASP(A8) | 20 | なし |
| 楽天アフィリエイト | 11 | なし |
合計73本。1本も計測していませんでした。
GA4 の画面には「イベント数 0」と正しく出ます。0 が出る以上、レポートは何も間違っていません。「まだ何も送っていない」と「送ったが 0 だった」は、GA4 の画面上で区別が付かないというのが本質でした。
対策として全リンクに委譲ハンドラを1本入れました。
document.addEventListener('click', (e) => {
const a = e.target.closest('a[href^="http"]');
if (!a) return;
const host = new URL(a.href, location.href).host;
if (host === location.host) return; // 内部リンクは対象外
if (typeof gtag !== 'function') return; // 未ロードでも例外を投げない
gtag('event', 'outbound_click', {
dest_host: host,
dest_url: a.href,
from_page: location.pathname,
});
}, { capture: true });
ポイントは3つです。
- 委譲にする。リンクは後から増えるので、個別に付けると必ず付け忘れます(実際73本のうち手作業で付いていたのは0本でした)
-
gtagが無くても例外を投げない。広告ブロッカーでgtagが落ちている環境で、計測コードがUIを壊すのが最悪です -
dest_hostを別パラメータで持つ。URL全文だけだとカーディナリティが高すぎて、GA4のレポートで束ねられません
0-2. 「まだ測れていない」0
新しく12ページ公開した翌日に Search Console を開いたら、12面中11面が表示 0 でした。
「新設ページのSEOは効かなかった」と判定しかけて、止めました。理由は2つあります。
- Search Console のデータには約2日のラグがある。9月2日時点で最新のデータ日は 8月30日でした
- 日付境界が太平洋時間(PT)。8月31日 00:19 JST に公開したページが、Search Console 上では 8月30日に計上されていました
つまり公開後に「表示され得た日数」は最大1日。この 0 は「効かなかった 0」ではなく 「まだ露出していない 0」 です。ここで No-Go を確定させていたら、まだ何も起きていない施策を失敗として畳んでいました。
判定日を6日後にずらしました。
0-3. 「期間が違う」0
Search Console の検索パフォーマンスの URL に、期間パラメータを付けて開いていました。
.../performance/search-analytics?resource_id=sc-domain%3Aexample.com
&start_date=2026-08-20&end_date=2026-09-02
このパラメータは無視されます。 既定の28日間にフォールバックしていて、実際に返っていたのは 8/3〜8/30 でした。指定した14日間ではなく、28日間の合計を「直近の数字」として読んでいたことになります。
確かめ方は1つだけです。
&breakdown=date
日別に落とせば、先頭行と末尾行で実際の期間が分かり、同時に最後にデータがある日も分かります。以後、期間を触ったら必ず最初に breakdown=date を見るようにしました。
0-4. 「粒度が違う」数字
コミットメッセージに「あるページのクエリが表示20・平均掲載順位6.6」と自分で書いていました。
後から breakdown=page で確認したら、そのページの28日間の表示回数は 0。ページ表に1行も出てきません。
20という数字は、そのクエリに対するサイト全体の表示回数でした。クエリ単位の数字を、ページ単位の実績として引き継いでいたわけです。
Search Console はクエリ・ページ・国・デバイスで別々に集計します。クエリの数字とページの数字を掛け合わせた行は存在しません(フィルタを掛ければ交差は取れますが、素の表を並べただけでは交差になりません)。
0-5. 「二重に数えた」数字
逆方向の間違いもありました。
同じアフィリエイトリンクのクリックで、aff_click と rakuten_click の2つのイベントが同時に飛んでいるサイトが1本ありました。片方は旧実装の残骸です。
このとき、レポートで両方を足すとクリック数がちょうど2倍になります。しかも「2倍」は一見それらしい数字なので、気づく手がかりがありません。
見つけ方は単純で、本番のDOMで gtag をスタブに差し替えて1回クリックするだけです。
// 本番ページの devtools で。実HITは1件も送らない
const sent = [];
window.gtag = (...args) => sent.push(args);
// …実際にリンクを1回クリック…
console.log(sent); // 1クリックで2件出たら二重送信
「実装したから送られているはず」と「1クリックで何件送られるか」は別の問いです。後者は本番でしか確かめられません。
0-6. 「基準を知らない」0
最後は Qiita 自体の話です。この1本前に投稿した記事が、2.2日で PV 544 / LGTM 0 / ストック 0 でした。
「読まれているのに反応が1件も無い=中身が刺さっていない」と読みかけました。
その前に、比較対象を実測しました。Qiita API は認証を付けると自分の記事の page_views_count が返ります(無認証だと null)。他人の記事のPVは非公開ですが、LGTM とフォロワー数は誰でも取れます。
# 同じタグの直近1週間の記事を200本取る
curl -s -H "Authorization: Bearer $TOKEN" \
"https://qiita.com/api/v2/items?query=tag:ClaudeCode+created:%3E2026-08-25&per_page=100&page=1"
結果(ClaudeCode タグ・2026/08/27〜09/02・n=200):
| 指標 | 値 |
|---|---|
| LGTM 中央値 | 0 |
| LGTM が 0 の記事の割合 | 69.5% |
| LGTM 5以上の記事の割合 | 6.5% |
LGTM 0 は例外ではなく中央値でした。
さらに、LGTM 5以上の記事(13本)と LGTM 0 の記事で著者のフォロワー数を比べると、
| 群 | 著者フォロワー数の中央値 | 本文長の中央値 |
|---|---|---|
| LGTM 5以上 | 109 | 4,654字 |
| LGTM 0 | 0.5 | 5,348字 |
本文長では差が付かず(LGTM 0 の群のほうがむしろ長い。14,529字で19 LGTM の記事もありました)、フォロワー数のほうにはっきり差が出ました。相関であって因果ではありませんが、少なくとも「0 だから中身が悪い」という読み方は、この分布の上では支持されません。
比較対象を取る前に自分の 0 を解釈しようとしたのが間違いでした。
本物の 0
ここまでの6件は読み違いでしたが、1件だけ本物の 0 がありました。
その 544 PV の記事から、自分のサイトへの流入は 0 です。
| 実測項目(2026/08/31〜09/02) | 値 |
|---|---|
| 記事のPV | 544 |
はてなブックマーク数(bookmark.hatenaapis.com/count/entry) |
0 |
GA4:qiita.com からの参照セッション(自分の3プロパティ合計) |
0 |
| 記事から張った先(別サービスの記事)の全期間PV | 12 |
理由は測る前から構造で決まっていました。その記事に置いた外部リンクは1本だけで、しかも GA4 を入れていない別サービス宛てだったからです。GA4 を入れているサイトへのリンクは、記事中に1本もありませんでした。
つまり「送客が 0 だった」のではなく、送客が発生し得ない設計だった。0-1 とまったく同じ間違いを、今度はリンクの張り先でやっていたことになります。
ついでに rel も確かめました。
curl -s "https://qiita.com/<user>/items/<id>" \
| grep -oE '<a[^>]*href="https://[^"]*"[^>]*rel="[^"]*"' | head
# => rel="nofollow noopener"
外部の記事プラットフォーム側も同じく rel="nofollow" でした。SEO評価は渡りません。 外部プラットフォームは被リンク源ではなく、referrer を運ぶ導線として見るのが正しかったです。referrer 自体は noopener では落ちません(noreferrer なら落ちる)。したがってリンク先に GA4 が入っていれば qiita.com / referral として計上されます。入れていれば、の話ですが。
判定前チェックリスト
同じことを繰り返さないように、数字を読む前に通すリストを作りました。
- その指標を送るコードは実在するか(本番DOMで1回発火させて確認したか)
- 1アクションで何件送っているか(二重送信していないか)
- データの最終日はいつか(Search Console は約2日ラグ・PT境界)
-
その期間指定は本当に効いているか(
breakdown=dateで実期間を見たか) - その数字の粒度は何か(クエリ/ページ/サイト全体を混ぜていないか)
- 比較対象の分布を取ったか(0 が異常値なのか中央値なのか)
- 測りたい経路にリンクは存在するか(0 は「踏まれなかった」ではなく「無かった」かもしれない)
6件は、それぞれ別の行に対応しています。逆に言うと、1行でも飛ばすとどれかを踏みます。
計測は、実装より読み方のほうが壊れやすいという話でした。特に 0 は「異常が無い顔をした異常」 なので、エラーにならないぶん厄介です。
なお、こうやって数字を洗い直す作業自体を自動化した記録(実装とプロンプトを全部載せています)は別途こちらにまとめてあります → https://note.com/fujiken818/n/n7f126d2e8522?utm_source=qiita&utm_medium=article&utm_campaign=note_ai_980&utm_content=qiita_002
この記事でやった「外部リンクと計測の全数チェック」は、他の方のサイトでも単発の監査として引き受けています(1サイト ¥55,000)。表示速度ではなく、可読性・タップ標的・重複・計測もれを実測して、直す順に並べるやり方です。見る項目と料金の目安 → https://mainichi-lab.com/contact?utm_source=qiita&utm_medium=article&utm_campaign=work_inquiry&utm_content=qiita_002_contact