[🛠] 公開ブログ統計ページを作る
✨ 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のcounters、page_counters、dedupe_viewsを増やす。Google Analyticsはbaselineで、以後の増分はD1に積む構造だった。
問題は、この構造が正直すぎたことだ。
ブラウザがページを開けば数える。
ローカル開発サーバーでもendpointはproduction Workerを向いていた。Worker側の許可originにもlocalhost:4000と127.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/だ。
コメントする