2026.06.09 (火)

✨ GPT-5.5の要約  

不自然に跳ねた訪問カウンターを追跡し、ローカル作業がカウントされないようにして、Cloudflare WorkerとD1で公開集計型analyticsページを追加した記録。

訪問者カウンターがおかしかった。

1日の訪問数が急に80を超えた。

検索流入かもしれない、と流すこともできた。でも違和感があった。ここ数日、ブログをかなり直していて、AI作業中にもローカルサーバーと公開URLを何度も開いていた。

だから疑った。

これは本当の訪問なのか。

それとも、自分の作業までカウントしているのか。

カウンターが正直すぎた

まず構造を見直した。

/ja/devlog/github-pages-blog/github-pages-blog-visitor-counter/で作った訪問者カウンターは、Cloudflare WorkerとD1を使っている。

ページが読み込まれるとJSがWorkerに/trackを送り、WorkerがD1のcounterspage_countersdedupe_viewsを増やす。Google Analyticsはbaselineで、以後の増分はD1に積む構造だった。

問題は、この構造が正直すぎたことだ。

ブラウザがページを開けば数える。

ローカル開発サーバーでもendpointはproduction Workerを向いていた。Worker側の許可originにもlocalhost:4000127.0.0.1:4000が入っていた。

つまりローカルプレビューでも本番D1カウンターを増やせた。

dedupe基準もvisitorId + pathだった。同じブラウザで同じページを10分以内に何度開いても1回だが、違う記事を次々開けば全部page viewになる。

D1を見ると露骨だった。

day:2026-06-06 = 14
day:2026-06-07 = 38
day:2026-06-08 = 81
day:2026-06-09 = 5

2026-06-08には、古いDaily Reviewやnaver-*記事が広く1回ずつ触られていた。実読者というより作業中の確認に近い。

ローカル作業は表示だけして、数えない

最初の修正は単純だった。

ローカルでは統計を表示しても、/trackは送らない。

localhost
127.0.0.1
::1

この環境ではクライアントJSがtrackingを飛ばさない。

ただしクライアントだけを信じるのは弱い。JSが壊れたり手動リクエストが来たりすれば、同じ問題が戻る。だからWorker側でも/trackはproduction originだけを数えるようにした。

TRACK_ALLOWED_ORIGIN=https://hyuk.blog

ローカルoriginからのリクエストはorigin_not_trackedで無視される。

本番画面を確認するとき用にopt-outも用意した。

?hyuk_no_track=1
?visitor_tracking=off
?visitor_tracking=on

隠し管理機能ではなく、公開ページを確認するときにカウンターを汚さないためのスイッチだ。

統計は隠さなくてもよかった

最初は自分だけが見るanalyticsページを考えた。

でも、そうする必要は薄かった。

IP原文、User-Agent原文、full referrer URL、単発イベントログを保存しなければいい。公開できる値だけを集計すれば、統計ページ自体も公開できる。

方向を変えた。

非公開ログページではなく、誰でも見られるブログ統計ページ。

/analytics/

上部ナビゲーションに追加し、サイドバーの小さな訪問統計からもリンクした。

D1には集計だけ積む

新しいテーブルは単純だ。

analytics_daily_dimensions

date
dimension
value
count
updated_at

page viewが実際に数えられたとき、Workerは公開可能なdimensionを分類し、日単位で加算する。

扱うdimensionはこういうものだ。

page
content_group
locale
country
region
continent
colo
asn_org
device
viewport
browser
os
language
client_timezone
color_scheme
connection
traffic_source
referrer_domain
hour
weekday

大事なのは、保存しないものだ。

referrerはURL全体ではなくdomainだけ。User-Agentは原文ではなくbrowser、OS、deviceに分類する。IPは保存しない。

厳密な分析基盤ではない。公開ブログ用の流れを見る板だ。

今日、月、全体、直接期間

最初は既存カウンターと同じく今日 / 月 / 総だけを考えていた。

でも統計ページなら、特定の日付や期間も必要だ。

/analytics APIはこう受け取る。

/analytics?range=today
/analytics?range=month
/analytics?range=total
/analytics?range=custom&start=2026-06-09&end=2026-06-12

ここでのtotalは、analytics_daily_dimensionsが集まり始めた2026-06-09以降の詳細集計だ。サイドバーの総閲覧数は引き続きGA baselineを含む。

この2つは混ぜなかった。

モバイルで先に読める形にした

統計ページはモバイルで崩れやすい。

大きな表にするとすぐ窮屈になる。だから表ではなく、棒グラフ風のリストカードにした。

上には数字を4つだけ置く。

選択期間
今日
今月
全体

下にはdimensionごとのカードを並べる。

モバイルでは期間ボタンが2行になり、KPIは2列、詳細カードは1列になる。直接期間入力も縦に積む。

デスクトップではKPIが1行、詳細カードが2列に広がる。

ブラウザで確認したところ、横overflowはなく、console errorもなかった。

確認したこと

確認はこうした。

node --check cloudflare/ga-stats-worker.js
node --check assets/js/custom/visitor-stats.js
node --check assets/js/custom/analytics-dashboard.js
bundle exec jekyll build
npx wrangler d1 execute hyuk-blog-view-counter --remote --file cloudflare/schema.sql
npx wrangler deploy --keep-vars

デプロイ後、/analytics?range=todayも正常に返った。

新しい集計テーブルはまだ空だった。これは当然で、詳細analyticsはデプロイ後の実際の公開訪問から積まれる。

ローカルoriginのtrackingも確認した。

{"status":"ignored","reason":"origin_not_tracked"}

これでローカル確認はproduction counterを増やさない。

訪問者カウンターから公開analyticsへ

最初は小さな数字3つだった。

今日、月、総。

でも実際に運用すると、数字そのものより、その数字が汚れていないことのほうが重要だった。そして公開ブログなら、隠したログページより安全な集計の流れを見せるほうが合っている。

これは会計帳簿ではない。

でも、もう少し役に立つ質問には答えられる。

どの記事が読まれているか。

どの国から来ているか。

モバイルが多いのか、デスクトップが多いのか。

検索なのか、直接流入なのか。

そして自分の作業由来のノイズをどこまで止めるべきか。

それを見るためのページが/analytics/だ。

コメントする