Website analytics
How vanityURLs.link sends privacy-conscious server-side analytics from the site Worker.
The website uses Umami for visitor analytics. Events are sent server-side from the Cloudflare Worker instead of through a browser JavaScript snippet.
Privacy model
The website analytics setup is intentionally small:
- no analytics cookies
- no client-side analytics script
- no
localStorageorsessionStoragefor tracking - no browser fingerprinting
- no full visitor IP address sent to Umami
The Worker truncates visitor IP addresses before forwarding analytics data: IPv4 to /24 and IPv6 to /48. That preserves coarse geography while removing household-level precision.
Keep public privacy pages in sync
Why server-side
| Concern | Browser analytics script | Worker-side analytics |
|---|---|---|
| Third-party script | Required | Not required |
| Cookies or browser storage | Often present | Not used |
| CSP impact | Requires analytics script and network allowlists | No browser-side analytics exception |
| Ad blockers | Often block the script | Browser never makes the analytics request |
| Bot behavior | Depends on browser JavaScript execution | Worker can tag known bots deliberately |
The tradeoff is that analytics behavior lives in Worker code and needs tests.
Request flow
flowchart LR A["Visitor
request"] --> B{"Request kind"} B -->|"HTML page
request"| C["Worker
fetches HTML"] C --> D["HTML response
and file returns"] C --> E["Analytics
module"] E --> F["Umami records
event"] B -->|"Static asset
request"| G["No analytics
event"]
HTML requests pass through src/worker.mjs, which uses ctx.waitUntil(...) to send analytics without delaying the visitor response. Static asset requests should not produce analytics events.
Event names
| Event | When it appears |
|---|---|
| Plain pageview | Normal HTML request |
404 | The generated response status is 404 |
bot | The user agent matches a known crawler pattern |
campaign | The URL contains standard utm_* parameters |
UTM parameters are stripped from the recorded page URL and moved into event data so the Pages view does not fragment into many query-string variants.
Required configuration
| Variable | Location |
|---|---|
UMAMI_WEBSITE_ID | Cloudflare runtime secret for the main web site |
UMAMI_WEBSITE_ID2 | Cloudflare runtime secret for brand.vanityurls.link; falls back to UMAMI_WEBSITE_ID when absent |
UMAMI_ENDPOINT | wrangler.toml [vars] |
Use runtime secrets, not build-time secrets
Debugging
When events disappear:
- Confirm the latest Worker deployed.
- Confirm
UMAMI_WEBSITE_IDis present as a runtime secret. - Confirm
UMAMI_WEBSITE_ID2is present when debuggingbrand.vanityurls.linkas a separate Umami site. - Confirm
UMAMI_ENDPOINTpoints at the intended Umami endpoint. - Check Cloudflare Workers Logs for the HTML request.
- Test from a browser that is not logged into the Umami dashboard.
- Compare named events in Umami’s Events tab before assuming Pages counts are wrong.
If diagnostic logging is temporarily added to src/worker.mjs, remove it before production cleanup. The logs can reveal operational details and add noise to every diagnostic request.
Changing analytics behavior
When you change UTM handling, bot detection, payload shape, or privacy behavior:
- Edit
src/worker.mjs. - Add or update tests in
src/worker.test.mjs. - Run
npm test. - Run
npm run build. - Deploy through the normal Git flow.
- Watch Cloudflare Workers Logs after deployment.