---
title: "heatmap-analytics — self-hosted behaviour analytics on Workers + D1"
description: "Case study: a full analytics backend on Cloudflare Workers and D1 — schema, idempotent ingest, token-gated read APIs, a nightly retention cron, and a 1.7KB tracker."
canonical: "https://asmyshlyaev177.dev/projects/heatmap-analytics"
image: "https://asmyshlyaev177.dev/og_image.png"
---

[All projects](https://asmyshlyaev177.dev/projects.md)

A backend I designed, deployed and run — schema to cron

# heatmap-analytics

Self-hosted click and hover heatmaps, scroll depth, rage-click detection and ghost-cursor session replay, rendered on the live page. No cookies, no third-party scripts, and a tracker that is 1.7KB gzipped.

## The challenge

Hosted analytics wants cookies, a third-party script and a per-seat bill, and none of the products answer the question I actually had: where do people stop reading. Doing it myself meant owning the whole path — what the browser sends, what the database stores, and who is allowed to read it back.

## My role

Sole author. Designed the SQLite schema, the ingest endpoint and the read API, and deployed it to Cloudflare Workers with D1. Events batch through one \`sendBeacon\` and are idempotent on a \`(pageview, sequence)\` unique constraint, so a retried flush cannot double-count. Reads are token-gated; a nightly cron purges past the retention window.

## Result

Running on this site. Every event is anchored to a CSS selector rather than to page coordinates, so heatmaps survive responsive breakpoints and content edits, and the tracker skips \`navigator.webdriver\` browsers so Playwright and Lighthouse runs never pollute the data.

## Built with

-   Cloudflare Workers
-   D1
-   SQL
-   TypeScript
-   Node.js
-   Analytics

[Source](https://github.com/asmyshlyaev177/heatmap-analytics)

I'm available for full-time remote work in EU timezones. Happy to talk through anything here in more detail.

[Get in touch](https://asmyshlyaev177.dev/#contact)

---

Site summary for LLMs: <https://asmyshlyaev177.dev/llms.txt> · Blog: <https://asmyshlyaev177.dev/blog>
