F1ReplayTiming
F1 리플레이와 실시간 트랙 데이터·텔레메트리를 시각화
Cloudflare Worker 대시보드에서 웹 분석 트래픽과 Google Search Console 키워드를 한눈에 확인
Cloudflare Worker dashboard aggregating Web Analytics traffic, Google Search Console keywords, and Discourse forum activity across a domain portfolio into daily D1 snapshots with anomaly signals and S
데이터JavaScript · ShellGitHub @DavidVeksler온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Cloudflare Worker dashboard aggregating Web Analytics traffic, Google Search Console keywords, and Discourse forum activity across a domain portfolio into daily D1 snapshots with anomaly signals and SEO opportunity ranking.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A serverless Cloudflare Worker that aggregates Cloudflare Web Analytics (RUM traffic + referrers), Google Search Console (keywords, CTR, ranking position), and Discourse forum activity for a portfolio of domains into one self-hosted daily dashboard — no external analytics SaaS, no client-side tracking script. A Cron Trigger pulls everything nightly, writes a snapshot per domain into D1 (SQLite at the edge), and renders instantly from stored history with 14-day sparklines, anomaly detection, and ranked SEO opportunities. Live example: https://stats.davidveksler.com.
david-stats-cf-serp).Cron 13:00 UTC ─┐
├─> Worker (src/index.js runDaily)
/run?key=… ───┘ ├─ pullTraffic() → Cloudflare GraphQL (all 4 accounts)
├─ querySearchSummary()/queryKeywords()/queryPages() → Google Search Console
├─ write snapshot → D1 (traffic, referrers, queries, pages)
└─ sendNtfy() → ntfy.sh/david-stats-cf-serp
GET / → loadDashboard() reads D1 → renderDashboard() HTML
GET /api/json → same data as JSON (`period`, `domain`, and `sort` query params)
GET /health → "ok"
GET /run?key=… → manual re-pull (needs REFRESH_KEY secret)
Files: src/config.js (domains + accounts), src/cloudflare.js (RUM pull),
src/gsc.js (Search Console + JWT auth), src/render.js (HTML), src/index.js (handlers).
Edit SITES in src/config.js. host = the Cloudflare Web Analytics requestHost;
gsc = the exact Search Console property string (sc-domain:… or a URL prefix).
Set gscPageFilter to an RE2 expression when a broad property needs to be limited to
specific page URLs. The davidveksler.com card uses this to exclude subdomains.
The nightly job runs headless, so it can't use an interactive Google login. It uses a
service account (GSC_SA_KEY, now set). If GSC_SA_KEY is ever missing, traffic/referrers
still work fully and keywords are simply skipped (the ntfy push notes it). To rotate the key,
repeat these steps and run ./deploy.sh --gsc-key path/to/key.json.
client_email (looks like [email protected]) → Restricted (read) is enough.
Do this for every configured property.export CLOUDFLARE_API_TOKEN=<your-cf-token>
wrangler secret put GSC_SA_KEY < path/to/key.json
curl "https://stats.davidveksler.com/run?key=$REFRESH_KEY"
# gscOk should be true; the ntfy warning disappears
Use the deploy script — idempotent, parses the CF token from ~/Projects/.cloudflare.env,
ensures secrets, deploys, and smoke-tests:
./deploy.sh # install, ensure secrets, deploy, verify /health
./deploy.sh --refresh # ...then trigger a live pull (/run) and print the result
./deploy.sh --gsc-key key.json # also (re)set the GSC service-account secret
./deploy.sh --schema # also (re)apply schema.sql — needs a D1:Edit-scoped token
Refresh the stored stats without deploying. The command reads the gitignored
.deploy/refresh_key.txt, sends a browser user-agent to avoid WAF rule 1010, prints the
JSON result, and exits nonzero when the request or GSC portion fails:
npm run refresh
./scripts/refresh-stats.sh
.\scripts\refresh-stats.ps1
Use npm run refresh -- --help for endpoint, key-file, timeout, and environment overrides.
From Windows PowerShell, use the Git Bash wrapper. It avoids accidentally selecting WSL's older Node runtime and forwards the same options:
.\deploy.ps1
.\deploy.ps1 -Refresh
.\deploy.ps1 -Schema
.\deploy.ps1 -GscKey C:\path\to\key.json
Manual bits:
export CLOUDFLARE_API_TOKEN=<first token in ~/Projects/.cloudflare.env>
npm run deploy # raw wrangler deploy
npm run tail # live logs
Secrets (set once via wrangler secret put, or auto-provisioned by deploy.sh):
CF_API_TOKEN — Cloudflare token with Account Analytics: Read (the Worker's GraphQL calls). ✅ setREFRESH_KEY — protects GET /run and POST /ingest-bing. ✅ set (saved locally in .deploy/refresh_key.txt, gitignored)GSC_SA_KEY — Google service-account JSON. ✅ setBING_API_KEY — Bing Webmaster Tools API key. Set for /bing-sites and the (expected-to-fail,
diagnostic-only) /run-bing — see below for why the real nightly Bing pull doesn't use it from here.Note: the analytics token deploys the Worker and binds D1 at runtime, but is not scoped for D1 management writes, so
d1 execute --remote/--schemacan fail with codes10000or7500. Additive dashboard migrations are also applied idempotently through the Worker's runtime D1 binding before a data pull.
Vars (in wrangler.jsonc): NTFY_TOPIC = david-stats-cf-serp.
The Bing pull runs in GitHub Actions, not on a Worker cron (.github/workflows/bing-pull.yml,
scripts/bing-pull.mjs) — Bing throttles every call routed through Cloudflare Workers' shared egress
IPs (ErrorCode 17 "ThrottleIP"), confirmed 2026-09-11 to be independent of the account, key, or
pacing. The Action fetches from Bing and POSTs the results to POST /ingest-bing?key=<REFRESH_KEY>,
which only writes them to D1. Requires two GitHub repo secrets (Settings → Secrets and variables →
Actions): BING_API_KEY and STATS_REFRESH_KEY (the same value as the Worker's REFRESH_KEY).
The Action posts to the Worker's *.workers.dev URL, not stats.davidveksler.com — that zone has
Bot Fight Mode on, which JS-challenges the GitHub runner's IP (a separate issue from the Bing-side
throttle above, found right after that fix shipped). workers_dev: true in wrangler.jsonc serves
the same Worker there too; REFRESH_KEY still gates /ingest-bing regardless of hostname. See the
AGENTS.md gotcha for the full diagnosis.
stats-dashboard on account David Veksler's Websites (556c237bf8cb62edb8f7b401499bb7a9)stats-dashboard (2f5ea431-472e-462f-94a4-b396576c1a5b)stats.davidveksler.com0 13 * * *davidveksler.com GSC property is a broad sc-domain: property, but its dashboard query
filters the page dimension to root-domain URLs. This keeps its keywords separate from
cheatsheets.davidveksler.com without requiring another GSC property.DELETE if desired.