サイトを公開して5日目に、GA4 のホーム画面を開きました。アクティブユーザー66人、表示回数168、平均エンゲージメント時間31秒。公開したばかりの個人サイトとしては、悪くない数字に見えます。
そこから2週間ほど同じ画面を見続けて、分かったことを先に書きます。この数字を、そのまま記事を読んだ人数にはできません。ボットを疑って内訳を開いていったら、後日の集計では自分の開発環境が9ユーザーとして記録されていました。残りのアクセスが人かボットかは、特定できていません。
以下の観測値は2026年8月当時のものです。66ユーザーとlocalhostの9ユーザーは集計期間が違うため、差し引いて読者数を出すこともできません。2026年9月8日に、観測から言える範囲と、現在の計測コードの説明を訂正しました。
この記事は、自分のプロパティに出た数字と、公式ドキュメントに書いてあることを並べたものです。ボットの見分け方を教える記事にはなりません。むしろ、見分けられない範囲がどこまでかを公式に確かめる記事です。数字はすべて自分で見たもので、公式の記述と自分の観測はそのつど分けて書きます。
読者数を確かめたくなった5つの内訳
公開5日目、2026年8月18日に取得した数字です(期間は7/21〜8/17、実質5日間)。
- セッション 81
- アクティブユーザー 66
- 表示回数 168
- 平均エンゲージメント時間 31秒
この4行だけでは、誰が何を読んだかは分かりません。そこで、内訳を5か所ひらきました。
1. 新規とアクティブが同じ66だった
アクティブユーザーが66、新規ユーザーも66でした。当時はこれを「再訪がゼロ」と読みましたが、この2つは定義が異なるため、同数というだけではそう断定できません。Google公式のユーザー指標の説明を2026年9月8日に確認しました。
再訪を見たいならリピーターの指標を別に確認する必要があります。初めて訪れて記事を読む人もいるので、再訪の有無を読者かどうかの境目にはできません。
2. 日本語のサイトに、米国から35人
国別のアクティブユーザーは US 35 / JP 14 / PL 5 / CA 3 / DE 3 / UK 3 / KR 1 でした。日本語だけで書いた、日本国内のVPSの話しか載っていないサイトです。
その4日前、公開2日目に見たときは、もっとはっきり出ていました(2026年8月14日取得・過去28日)。
| 国 | ユーザー | エンゲージのあったセッション | 平均エンゲージメント時間 |
|---|---|---|---|
| Japan | 12 | 20 | 3分20秒 |
| United States | 19 | 0 | 0秒 |
| Poland | 5 | 0 | 0秒 |
| 他5か国 | 各1〜2 | 0 | 0〜3秒 |
ホーム画面には「アクティブユーザー44人」と出ていました。この表でエンゲージのあったセッションを持っているのは Japan の20セッションだけです。ただし、国もエンゲージメントも、人間が読んだ証明にはなりません。後述する自分の作業も混ざり得る集計でした。
3. Direct が93.8%
同じ8月18日のチャネル別です。
| チャネル | セッション | エンゲージメント率 | 平均エンゲージメント時間 |
|---|---|---|---|
| Direct | 76(93.83%) | 26.32% | 26秒 |
| Organic Search | 4(4.94%) | 50% | 2分20秒 |
| Referral | 1 | 100% | 1秒 |
Organic Search は4セッションですが、平均エンゲージメント時間は Direct の5倍以上ありました。流入経路によってこの指標に差があることは分かります。ただし、4セッションという小さな集計から、検索以外は読まれていないとは言えません。
4. トップと記事で、平均時間が違う
同じ日のページ別の滞在です。
/(トップ) 98表示・7秒/posts/vps-first-things-after-signup/10表示・2分57秒/posts/railway-compose-not-portable/14表示・1分43秒/posts/conoha-vps-2gb-limits/20表示・57秒/posts/docker-compose-ports-override-merge/6表示・4秒
表示回数168のうち98がトップです。全体の約58%がトップページに集まり、平均時間は7秒でした。一部の記事では分単位になっていますが、記事でも4秒の行があります。
この違いは、ページごとの使われ方を調べる手がかりになります。ただし平均値から各訪問者の行動は再現できず、トップの訪問者と記事の読者をこの表だけで分けることはできません。
5. 90%到達のscrollが7ユーザー分あった
同じ日、GA4 の scroll イベントを見たら7ユーザー分ありました。拡張計測の scroll は、各ページで縦方向の90%地点が初めて可視になったときに発生します。90%を読了したという意味ではありません。公式の拡張計測イベントを2026年9月8日に確認しました。
時間とは別の手がかりになりますが、末尾へすぐ移動することもできます。66や44というユーザー数より小さいからといって、そのまま読者数にはできません。
それでも、これを「読者は7人だった」とは書けません。 理由が3つあります。
- 90%のスクロールを、人間の証明にはできません。 この記事の後半で見るとおり、JavaScript を実行するクローラーは実在します
- この7人には、私自身が混ざっている可能性があります。 自分の開発環境が計上されていたと気づくのは、この11日後です
- 別の日の記録では、
scrollが17ユーザー分、日本からのユーザーは14でした。海外の利用者もあり得るので、この大小関係から計測異常やボットとは判断できません
手がかりは増えますが、証拠にはなりません。 ここが、この記事でいちばん言いたいところの入口です。
ホスト名の列を出したら、開発環境が9ユーザーに数えられていた
ここまでの5つを見て、私は「海外のクローラーだろう」と考えていました。別の混入にも気づいたのが、8月29日です。
GA4 の「ページとスクリーン」に、セカンダリ ディメンションでホスト名を足しました。期間は8/1〜8/28です。
| ホスト名 | 表示回数 | ユーザー | 平均エンゲージメント時間 |
|---|---|---|---|
prodnote.dev | 約228 | — | 記事は1〜3分 |
localhost | 15(約6%) | 9 | 0秒 |
prodnote.pages.dev | 7(約2.8%) | 7 | 0秒 |
localhost が15表示、9ユーザー。自分が開発サーバーで開いていた画面が、そのままユーザーとして数えられていました。
この15表示・9ユーザーは、私です。
原因はコードにありました。GA4 の測定IDを lib/site.ts の定数として書いていて、タグを出すかどうかの条件を、何も付けていませんでした。だから本番ビルドでも開発サーバーでも、同じIDでタグが動きます。このサイトの計測は、公開初日から自分の作業を含んでいました。
prodnote.pages.dev の7表示も、同じ話の親戚です。Cloudflare Pages はカスタムドメインとは別に pages.dev のURLを持っていて、そちらでも同じHTMLが配られます。
8月29日に直したのはコード側です。タグの出力に NODE_ENV が production かどうかの条件を足して、開発サーバーではタグそのものを出さないようにしました。9月8日には、本番ビルドを別のURLで開くプレビューも区別できるよう、実行時のorigin照合を追加しています。現在の条件は末尾で説明します。
GA4 には「デベロッパー トラフィックの除外」というデータフィルタもあります。ただし、ヘルプの説明はこれだけです。
デバッグモードを使用しているデベロッパーのアクティビティを除外します。
デバッグモードを使っていない普通の開発サーバーは、これでは消えません。GA4 の画面側にはもうひとつ、IPアドレスで除外する仕組みがあります。そちらも設定しましたが、それだけでは足りませんでした。
内部トラフィックの除外を設定したら、自分のIPではなかった
コード側を直したのと同じ8月29日に、画面側の内部トラフィック除外も設定しました。ここから先は、実際に触りながら書いています。
設定は2か所に分かれています。まず「管理 → データストリーム → タグ設定を行う → もっと見る → 内部トラフィックの定義」でルールを作ります。traffic_type は既定の internal のまま、マッチタイプは「IPアドレスが次と等しい」を選びました。
ここで終わりだと思ったのですが、定義を作っただけでは除外されません。「管理 → データ設定 → データフィルタ」に Internal Traffic という項目があり、それが有効になっているかを確認する必要があります。
確認が要る理由は、フィルタに「テスト」という状態があるからです。ヘルプにはこう書かれています。
テストデータ フィルタに一致するデータが [テストデータのフィルタ名] ディメンションに割り当てられ、フィルタ名の値が設定されます。このデータは Explore で利用できるため、データフィルタを有効にする前に検証できます。
テストのままだと、データはレポートから除外されません。印が付いて Explore で見えるようになるだけです。有効にして初めて、レポートから消えます。
では初期状態がどちらなのかというと、そこが分かりませんでした。公式ヘルプを2ページ当たりましたが、初期状態についての記載を見つけられていません。私の環境では、開いた時点で既に有効でした。だから「確認してください」としか書けません。
つまずいたのは、登録するIPアドレスのほうです。
正しい取り方は、実際に計測されるパソコンから自分で確かめることです。IPv4 と IPv6 の両方を見ます。
curl -s -4 https://ifconfig.me
curl -s -6 https://ifconfig.me
この環境では IPv6 のほうが返らず、外へは出ていませんでした。そのため、登録したのは IPv4 の1行だけです。IPv6 を持つ回線で IPv4 だけ登録すると、除外は効きません。両方を実測してから決めてください。
そして、登録したIPは、いつか変わります。多くの家庭用の回線では、契約者に固定のアドレスが割り当てられているわけではありません。変わった時点で、この除外は黙って効かなくなります。エラーは出ません。設定した記録だけが残ります。
この記事で書いてきたことが、そのまま自分に返ってきます。「設定した」と「効いている」は別です。除外したはずの数字が急に増えたら、まずIPが変わっていないかを疑ってください。
そして、このフィルタは遡りません。有効にする前に記録されたものは消えないので、この記事に出てくる数字も消えていません。
それでも、これだけでは根が止まりませんでした。IPで除外できるのは、同じ回線から見たときだけです。別のパソコンや別の回線で開発サーバーを開けば、また混ざります。止まるのはコード側の分岐のほうでした。
画面の設定とコードの修正は、どちらか一方では足りません。IPでの除外は回線が変われば効かなくなり、コードの分岐は既に入ってしまった数字を消せません。両方いります。
「9人」は人数ではない
この件で、もうひとつ確認できなかったことがあります。localhost の9ユーザーが、実際に何人なのかが分かりません。
私は1人で開発しています。WSL側のブラウザ、Windows側のブラウザ、シークレットウィンドウ、キャッシュを消した後。これらは GA4 から見れば別のユーザーになり得ます。画面上で内訳を分解する方法は、見つけられませんでした。
GA4 の「ユーザー数」は、人数の推定値です。9と出ていても、実際は1人かもしれません。この記事を書いていていちばん実感したのは、ここでした。
GA4 はボットを除外している。ただし「既知の」だけ
ここから公開情報です。取得はすべて2026年8月29日。
Google アナリティクス ヘルプの「既知の bot トラフィックの除外」には、こう書かれています。
In Google Analytics properties, traffic from known bots and spiders is automatically excluded.
自動で除外される、とはっきり書いてあります。ただし対象は known、つまり既知のものです。そして同じページの続きのほうが重要でした。
At this time, you cannot disable known bot traffic exclusion or see how much known bot traffic was excluded.
除外をオフにすることも、除外された量を見ることもできないと書かれています。これは「調べたけれど分からなかった」という話とは別のものです。公式が、できないと明記しています。
判定の材料は2つだとされています。
Known bot and spider traffic is identified using a combination of Google research and the International Spiders and Bots List, maintained by the Interactive Advertising Bureau.
Google 自身の調査と、IAB が管理するリストの組み合わせ。前者の中身は、どこにも書かれていません。後者は次の節の話になります。
なお、このヘルプページには掲載日も更新日も表示がありません。ここに書いたのは、2026年8月29日時点で表示されていた内容です。
そのリストは、年15,000ドルでFTPから配られている
IAB のページには、購読の条件が書かれています。
Access to the list is not available until payment and a signed data subscription agreement have been received.
支払いと署名済みの契約が済むまでアクセスできない、と。配布はFTP、購読は12か月単位です。同じページに載っていた2026年8月29日時点の価格は、IAB会員が年5,000ドル、アソシエイト会員が7,500ドル、非会員が15,000ドル。
つまり、GA4 が何を消したのかを確かめようとすると、年15,000ドル払ってFTPからリストを取るところから始まります。利用者の側から検証できる仕組みになっていません。
リストの中身の説明は、IAB が2019年6月に出したベストプラクティスのPDFにあります。ただし、このPDFは本文をそのまま取り出せない形式でした。以下は引用ではなく、私が読み取った要旨です。原文との照合は済んでいません。
- 判定は、ユーザーエージェント文字列の照合で行う
- 含めるリストと除外するリストの2本を、順に当てる
- 含めるリストのどれにも一致しないUAは、ボットとして扱う
cURLやwgetのようなHTTPクライアントの既定のUAは、除外側に置かれている。理由は、それらが既定でグラフィックスレンダラに繋がっておらず、人間の閲覧に由来すると仮定できないから- リスト自身が、誤判定は起こり得ると認めている
この要旨を読んで、私はブラウザと同じUAでJSを実行する処理が除外されるのか疑問を持ちました。ただし、PDFの未照合の要旨から、IABリストやGA4の判定結果までは確定できません。GA4はGoogle自身の調査も併用すると説明しています。
「ボットはJavaScriptを実行しない」はもう成立しない
GA4 にボットが出ない理由としてよく説明されるのが、これです。GA4 はブラウザのJavaScriptで測るので、JSを実行しないボットは記録されない、という話。
Cloudflare は自社のドキュメントで、実際にそう書いています。
Google Analytics does not record threats, bots, and automated crawlers because those requests typically do not trigger JavaScript.
これは Cloudflare の記述であって、Google の記述ではありません。また、原文の typically は一般的な傾向を述べています。自分の画面でボットを疑った数字だけでは、この説明への反証にはなりません。
一方、JSを実行するクローラーの例は公式にもあります。Google 検索セントラルの「JavaScript SEO の基本」には、こうあります。
Once Google's resources allow, a headless Chromium renders the page and executes the JavaScript.
Googlebot 自身がヘッドレスの Chromium でページをレンダリングしてJSを実行する、と Google が書いています。同じページには、HTTPステータス200のページを原則すべてレンダリングのキューに入れる、とも書かれています。
そのヘッドレスモードについて、Chrome の公式ドキュメントはこう説明しています。
Essentially, you can run Chrome without chrome.
外枠なしで Chrome を動かせる、という言い方です。他の機能はすべて制限なく使える、とも同じページに書かれています。
ここから先は未確認です。ヘッドレスの Chrome がJSを実行できることと、IABやGA4の除外を通り抜けて計上されることは別です。私が確認した公式資料では後者を裏付けられず、自分のサイトに来た米国の35ユーザーがヘッドレスブラウザだったとも確かめていません。
言えるのは、ここまでです。JSを実行するクローラーが実在することは、Google 自身の文書で確定しています。だから、JSを実行しないから安心という一般論は、もう使えません。
Direct が多い理由は、断定できない
Direct 93.8% を見たとき、私は理由を調べました。結果は、あまり気持ちのいいものになりませんでした。
Google のヘルプにある Direct の定義は、条件式としてはこれだけです。
参照元 - 完全一致 - 「(direct)」 AND メディアが「(not set)」または「(none)」
説明文のほうは、保存済みリンクから、またはURLを入力してアクセスする際のチャネル、となっています。そして、何が Direct に落ちるのかの列挙は、どこにもありません。
よく見かける説明、たとえばアプリ内のリンク、リファラを落とす環境、ボット。これらはこの条件から導いた推定であって、Google がそう書いているわけではありません。
ひとつ、調べて誤りだと分かった説明があります。「最近のブラウザはリファラを送らないから Direct が増える」というものです。Chrome の公式ブログによれば、既定のポリシーは strict-origin-when-cross-origin で、こう説明されています。
With this policy, only the origin is sent in the
Refererheader of cross-origin requests.
オリジンは送られます。リファラが丸ごと消えるわけではないので、この説明は成立しません。自分の Direct 93.8% の理由には使えませんでした。
Cloudflare に切り替えれば分かる、とも言えない
GA4 で見えないならサーバー側で見ればいい。このサイトは Cloudflare Pages に置いてあるので、私も最初にそう考えました。
ただし、ここは実機で確認していません。以下はドキュメントを読んだだけの範囲です。
まず、Pages の Web Analytics はサーバー集計ではありません。ドキュメントには、次のデプロイでJavaScriptのスニペットを自動で追加する、と書かれています。FAQには、ビーコンがページの読み込み完了時と離脱時に報告する、ともあります。GA4 と同じ、ブラウザ側のJSビーコンです。同じ盲点を持ちます。
ボットが見えるのは、ゾーン(Cloudflare でプロキシしているドメイン)側の集計のほうです。Zone Analytics のドキュメントは、無料プランで見られる数字に、正当なユーザーのリクエストに加えてクローラーと脅威が含まれる、と書いています。Security Analytics のドキュメントにはこうあります。
Security Analytics displays information about all incoming HTTP requests for your domain, including requests not handled by Cloudflare security products.
ゾーン単位の分析は全プランに含まれる、とも書かれています。一方、専用の Bot Analytics は Business と Enterprise 向けで、無料プランにはありません。
ドキュメント上の話は、ここまでです。このサイトの Cloudflare の管理画面を開いて確かめてはいません。pages.dev のほうは Cloudflare のゾーンではないので同じようには見えない可能性がありますが、それも未確認です。
A8 は12回、GA4 は1回
数字が合わなかったものが、もうひとつあります。2026年8月22日に、アフィリエイトのクリック数を両側から見ました。
- A8 の管理画面:今月のインプレッション 309、クリック 12
- GA4 のイベント
affiliate_click:1 - 発生件数 0件、CVR 0.00%
この差だけでは、実装の不具合かどうかも、どちらが取りこぼしたかも判断できません。現在のコードにはアフィリエイトクリック時の送信処理がありますが、それはGA4側での受信を保証しません。
2026年9月8日の実装を読むと、計測条件が無効なら送信処理はそこで戻ります。有効でも gtag が存在するときだけ呼び出し、例外が出ても遷移は止めません。JSやタグの読み込み、ブラウザーの制限、通信の成否も関わるため、送信処理を呼べたことと、レポートへ記録されたことは分ける必要があります。これらは差が生まれ得る条件であり、8月22日の差の原因を特定した結果ではありません。
A8 側は、リンク先が a8.net のURLです。そのURLがリクエストされた時点で計上されると考えるのが自然ですが、これは推定です。A8 のヘルプはクリック数の定義を書いているだけで、サーバー側で数えているのかJSで数えているのか、ボットのアクセスをどう扱うのかは書かれていません。
A8側の実装を未確認のまま、URLへのリクエストがすべて計上されると決めつけることもできません。この記録では両側の数字を別々に残し、GA4の1イベントをA8の成果や実際のクリック総数の代わりには使いません。
GA4 が3桁と言っている裏で、検索から来たのは3クリック
いちばん短く言えるのは、この2つの数字を並べたときだと思います。
2026年8月27日に Search Console の検索パフォーマンスを見ました。過去28日(7/31〜8/24、実質12日間)で、合計クリック数 3、合計表示回数 35、平均CTR 8.6%、平均掲載順位 9.9 です。
同じころ、GA4 のアクティブユーザーは3桁に届いていました。GSCで記録された検索結果からのクリックは3回です。クリックした実人数は、この値だけでは分かりません。
GSC が数えるのは検索結果でのクリックだけなので、GA4 のユーザー数とは別物です。それでも、公開直後にどちらを見るべきかは、この並びではっきりします。
調べても分からなかったこと
この記事で埋められなかった穴を、そのまま置いておきます。
- GA4 の判定材料のうち、Google 自身の調査の中身。 どこにも公表されていません
- 除外された件数。 公式に、見られないと書かれています
- IABリストの実際の中身。 年15,000ドル払わないと見られません
- localhost の9ユーザーが実際に何人か。 GA4 側で分解できませんでした
- A8 のクリック計測の実装。 公式に書かれていません
- Slack や Meta のプレビュー取得がJSを実行するか。 両社とも書いていません。リンクを貼ればプレビュー用の取得が走ることまでは公式にありますが、それがGA4に載るかは分かりません
- Cloudflare の管理画面での実際の見え方。 まだ開いていません
Internal Trafficフィルタの初期状態。 内部トラフィックの除外とデータフィルタの2ページを当たりましたが、記載を見つけられませんでした。私の環境では開いた時点で有効でしたが、それが既定なのかは分かりません
最初に見る列
公開直後の GA4 で、いまの私が最初に見るものを挙げます。総ユーザー数は、いちばん後回しでいい数字でした。
- ホスト名。 既定のレポートでは主ディメンションに選べません。セカンダリ ディメンションか探索で足す必要があります。見えていなかったのではなく、その列を出していませんでした
scrollの90%到達。 到達位置の手がかりとして使います。読了や人間の証明ではありません- 再訪。 リピーターの指標を別に見ます。新規とアクティブが同数でも、読者がいないとは言えません
- ページ別の時間。 トップと記事の使われ方を考える材料にします。平均値だけで個々の閲読は確定できません
- Search Console のクリック数。 GA4 の数字とは切り離して見ます
そして、自分の作業の混入を減らすための作業が2つあります。画面側では内部トラフィックの除外を設定し、登録するIPを自分で確認すること。コード側では、タグを読み込む条件を本番ビルドと本番originの両方に限ることです。本番サイトを自分で見る操作はコードの条件を満たすため、画面側の除外も別に確認します。
このサイトでは、測定IDは定数のままにしています。環境変数へ移すと静的生成時に値を渡す配線が増えるので、タグ側の条件を変えました。8月29日は process.env.NODE_ENV === "production" を追加しましたが、これだけでは同じ本番ビルドをプレビューURLで開いた場合も対象になります。
9月8日現在の analyticsEnabled() は、測定IDが空でないこと、本番ビルドであること、ブラウザー上で動いていること、window.location.origin が https://prodnote.dev と一致することを確認します。ProductionAnalytics はその条件を満たす場合だけタグを読み込み、クリック等の送信関数も同じ条件を使います。開発環境・pages.dev・その他のプレビューからはこの実装経由で送信しない構成です。これは9月8日時点のコードの説明です。過去に記録されたデータは、この変更後も残ります。
私は2週間気づかずに、自分をユーザーとして数えていました。
このサイトでは「表示と実態がずれる」話を何度か書いてきました。本番が開発モードで動いていた話と、正常なのにコンテナが赤かった話です。
→ docker-compose.prod.yml が効かない — 本番が開発モード
→ docker ps が unhealthy なのにサイトは正常に動いていた
今回はその計測版でした。しかも、ずれを疑っていた自分の環境が、ずれの一部でした。
サイトを作るところから同じ構成でやる場合は、こちらに書いてあります。
