Skip to main content
Case study

Fathom

YEAR
2025
ROLE
Designer & Engineer
STACK
TypeScript · Cloudflare Workers · D1 · Web Components

What I was trying to figure out: whether you can measure whether writing is working without spying on the people reading it. I wanted to know if my posts were actually being read — not opened, read to the end — and every tool that could tell me also wanted to follow my readers across the internet. That trade felt both unnecessary and gross. Fathom was me refusing it.

Context

Pageviews are a lie I'd been telling myself for years. A post can have ten thousand views and a two-second average attention span; another can have eight hundred views and hold every one of them to the last line. The first feels like success and is failure. I didn't need more analytics — I needed one honest number: how deep do people read, and where do they leave.

The catch is that the entire analytics industry pays for that answer with surveillance. Cookies, fingerprints, cross-site identity, a dossier on a stranger so I can learn that my third paragraph is boring. I found that genuinely unacceptable for a personal blog, which is supposed to be a generous thing. So the real problem wasn't measurement. It was measurement without a victim.

Approach

I built Fathom around aggregation at the edge. The embed is a tiny web component — three lines to install — that reports scroll depth as a stream of anonymous increments, not events tied to a person. A Cloudflare Worker catches them, buckets them, and throws away anything that could rebuild an individual. By the time data lands in storage, there's no "you" left to track; there's only "of the people who started this post, this fraction reached the halfway mark."

That constraint shaped every technical choice. No cookies meant no consent banner, which meant the script could be genuinely small and the reading experience stayed clean. Storing only aggregates meant the database was tiny and cheap and impossible to leak meaningfully, because there was nothing personal in it to leak. The privacy posture wasn't a marketing layer on top — it was the architecture.

Decisions

Read-depth as the headline metric. I demoted pageviews to a footnote and made "how far did they get" the number you see first. It reframes the whole question from did they arrive to did it land. Some people found that uncomfortable. Good — it's the uncomfortable number that's useful.

No per-person data, ever — by construction. I didn't add a privacy mode. I made it impossible to do otherwise. You can't accidentally turn on surveillance in Fathom because the code to assemble a profile doesn't exist. Privacy you can't switch off is the only kind worth advertising.

Edge-first, no origin server. Workers and D1 meant the whole thing runs at the edge with near-zero latency and near-zero cost. A personal-scale analytics tool shouldn't need a server humming in a datacenter; it should cost a few dollars a year and disappear.

Open source. The only way to make "we don't track you" credible is to let people read the code that doesn't. The repo is the proof; the privacy policy is just a summary of what you can verify yourself.

Outcome

Fathom runs on this site, and on a handful of other blogs whose authors care about the same thing. It told me, immediately and a little painfully, that my longer posts lose half their readers by the third screen — which changed how I write more than any writing advice ever has. That's the whole value: a true signal you can act on, bought without anyone's privacy as currency.

It's not a business and I don't intend to make it one; a privacy tool that needs to grow eventually has to compromise the thing it sells. It's a small public utility I maintain because I use it, and because the version of analytics I wanted to exist didn't.

Lessons

I spent too long making the dashboard pretty before I trusted the data. The chart polish was procrastination dressed as craft — the moment of value was the first honest read-depth number, and I delayed it by weeks chasing a nicer axis. Ship the ugly truth first.

I also learned that "privacy-first" is easy to say and only believable when it's structural. Early on I had a perfectly reasonable feature — session-level read paths — that would have required holding per-person data for a few minutes. I cut it. The temptation to peek is exactly the temptation the architecture has to make impossible, including for me.

Credits

Solo. Built on Cloudflare's edge stack, which did most of the hard work. The framing of "depth over breadth" I owe to a long argument with a friend who runs a much bigger blog and measures it the old way.

One number that matters: how far people actually read. Everything else is noise.
Three lines to install. No cookie banner, because there are no cookies.
Counts are aggregated at the edge. No profile of you is ever assembled.
Two posts with identical pageviews and completely different truths.