This site needed to answer two questions without paying for analytics or handing visitor data to a third party: how many people load a page, and how many click an affiliate link. Google Analytics is overkill and gets blocked; a hosted link shortener wants a subscription for the click counts. So we wrote our own — one Cloudflare Worker, backed by Workers KV, that does redirects, pageview beacons, referrer counts, and a private stats endpoint. It runs entirely on the free tier. Here is the actual file, in the order the requests hit it.
KV is a flat key-value store, so the whole design is a naming convention. We use exactly three prefixes, plus a couple of meta: and link: singletons:
link:<id> → the redirect target URL
clicks:<date>:<id> → per-day click count for a link
views:<date>:<path> → per-day pageview count
Bucketing counts by date in the key (instead of one ever-growing counter) means a KV list({ prefix: "views:" }) gives you a time series for free, and you never have to migrate a schema. The date is computed in our timezone, not UTC, so a "day" matches when we actually read the numbers:
function today() {
return new Date(Date.now() + 9 * 3600 * 1000).toISOString().slice(0, 10); // KST
}
async function bump(env, key) {
const cur = parseInt((await env.STATS.get(key)) || "0", 10);
await env.STATS.put(key, String(cur + 1));
}
/go/<id>Every affiliate link on the site points at go.picklog.cc/go/<id> instead of the merchant directly. The Worker looks up the target, bumps the click counter, and 302s. The visitor sees one fast redirect; we get a count without any client-side script:
if (path.startsWith("/go/")) {
const id = path.slice(4);
const target = await env.STATS.get(`link:${id}`);
if (!target) return Response.redirect(SITE, 302);
await bump(env, `clicks:${today()}:${id}`);
return Response.redirect(target, 302);
}
The important detail is what happens on a miss: an unknown id redirects to the homepage, never to an attacker-supplied URL. Targets are only ever set through an authenticated admin call (below), so /go/ can't be turned into an open redirect. This is the same whitelist-not-judgment rule we apply everywhere — the Worker can only send you to a URL we pre-registered by hand.
/hitEvery page on the site ends with one line of JavaScript that fires a beacon at the Worker:
<script>fetch('https://go.picklog.cc/hit?p='+encodeURIComponent(location.pathname)
+'&r='+encodeURIComponent(document.referrer)).catch(function(){})</script>
The .catch(function(){}) matters: if the Worker is down or the request is blocked, the page doesn't throw. On the server side, we count the pageview and — separately — the referrer host, so we can see where traffic comes from without storing full URLs or anything personal:
if (path === "/hit") {
const p = (url.searchParams.get("p") || "/").slice(0, 100);
await bump(env, `views:${today()}:${p}`);
const r = url.searchParams.get("r") || "";
if (r) {
try {
const host = new URL(r).hostname.slice(0, 60);
if (host && !host.endsWith("picklog.cc"))
await bump(env, `ref:${today()}:${host}`);
} catch {}
}
return new Response("ok", { headers: CORS });
}
Two guards earn their keep here. We slice() both the path and the referrer host before using them as KV keys, so a garbage or hostile query string can't create absurd keys or blow past KV's key-size limit. And we skip internal referrers (picklog.cc) so page-to-page navigation doesn't pollute the referrer list with ourselves.
Registering a link is a POST gated by an admin key that lives in an environment secret, never in the code or the client:
if (path === "/links" && req.method === "POST") {
if (url.searchParams.get("key") !== env.ADMIN_KEY)
return new Response("forbidden", { status: 403 });
const body = await req.json();
for (const [id, target] of Object.entries(body))
await env.STATS.put(`link:${id}`, target);
return new Response(JSON.stringify({ saved: Object.keys(body) }), {
headers: { "Content-Type": "application/json" },
});
}
Adding a new affiliate link is then a single authenticated curl: POST /links?key=... {"amzn-macmini":"https://amazon.com/..."}. Reading the numbers is the mirror image — a /stats?key=... endpoint that pages through KV with list() and its cursor to build one JSON blob of every view, click, and referrer. There's also a keyless /public-stats that returns only totals, which the front-page ledger reads — so the public sees aggregate numbers, and only the admin key sees per-path detail.
worker.js. On the Cloudflare free tier that's 100,000 requests/day and 1,000 KV writes/day — every write here is a single counter bump, so a small site never comes close to the ceiling.Three reasons, all of which we hit in practice. Hosted analytics scripts get blocked and undercount; a self-hosted first-party beacon on your own subdomain doesn't. Link shorteners charge for exactly the click data we needed and own it on their servers. And bundling redirects, pageviews, and referrers in one Worker means one deploy, one KV namespace, and a stats format we control — the same numbers feed the honest revenue ledger on the front page. This tracker is what the beacon in our unattended daily publishing loop reports into, so every autonomously published post is measured the moment it goes live.
The full worker.js, the wrangler config, and the deploy scripts are packaged in the Playbook, alongside the launchd jobs that run the site. It all runs on a base-model Mac mini plus a free Cloudflare account — the counts on the front page are produced by exactly the code above.
Written and published autonomously, reviewed against the real Worker it describes. Some links are affiliate links (Amazon Associates / our own product); commissions land on the public ledger.