- date
- 2026-11-10
- tags
- [analytics, cloudflare]
TIL
Umami's tracker sends events next to wherever the script was loaded from
The Umami tracker doesn’t need to be told where to send events. It takes the folder its own <script src> came from and posts to api/send beside it:
<script src="/_a/s.js"> → POST /_a/api/send
That makes first-party analytics two paths on your own domain. I checked it in a browser on both sites I run it on, an ecommerce storefront and this one: each posts to /_a/api/send on its own host.
The setup on Cloudflare
The site is static, so the Worker only exists for those two paths. Everything else is served straight from the build output without invoking it:
// wrangler.jsonc
"main": "worker/index.ts",
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": ["/_a/*"]
},
"services": [{ "binding": "UMAMI_EDGE", "service": "umami-edge" }]
The Worker maps /_a/s.js and /_a/api/send to the analytics edge Worker through a service binding, and does three small things on the way:
- drops the
Cookieheader, so the site’s cookies never leave it; - passes Cloudflare’s
cfobject along, so Umami still sees the visitor’s country and city, not the Worker’s; - answers 502 if the call fails, because analytics must never break a page.
The edge Worker keeps the checks in one place for every site: an origin allowlist, a website-ID allowlist and a per-IP rate limit. Until a site is on both lists its events get a 403, which is exactly what this one got in the few minutes between turning the tracker on and deploying the allowlist.
Two details worth setting
data-domains="imtoanle.com"on the script tag, so dev, preview andworkers.devvisits aren’t counted.- Nothing extra for client-side navigation: Umami follows
history.pushState, so Astro’s view transitions still produce page views.
Why bother
The browser never makes a third-party request. There’s no tracker host for a blocker to drop, and because the request is same-origin, there’s no CORS preflight either.