DEV Community

Cover image for I don't run analytics on my site. Here's what 273,000 requests told me anyway.
Rishab Dugar Jain
Rishab Dugar Jain

Posted on Originally published at rishabdugarjain.hashnode.dev

I don't run analytics on my site. Here's what 273,000 requests told me anyway.

Fizzdoc, my free PDF and photo toolkit, has no analytics script. No Google Analytics, no pixel, no cookie banner. The page isn't even allowed to make outside requests: its Content Security Policy blocks every connection that doesn't go back to fizzdoc.com.

That was the point. A tool that promises "your files never leave your device" shouldn't be quietly reporting on you either.

It also means I had no idea who uses it.

The only numbers I have are the ones every host sees anyway: Cloudflare's request logs, summed per day, with no cookies and no fingerprinting. So I took 30 days of them and tried to answer one question: how many of my "visitors" are actually people?

Here's what the dashboard says:

Last 30 days
Requests 273,490
Unique visitors 17,490
Visits 36,010
Page views 51,880
Data served 18.23 GB
Server errors (5xx) 0

30 days of requests on fizzdoc.com

36,000 visits for a side project sounds amazing. It isn't true. Let's take it apart.

Clue 1: the second most popular file isn't a page

Sort requests by path and the top of the list is:

Path Requests
/ (home page) 7,690
/robots.txt 2,990
/sw.js 2,750
/theme.js 1,990
/manifest.webmanifest 1,660

robots.txt is the second most requested file on the site. No human opens it on purpose. That's 3,000 requests from crawlers asking whether they're allowed in. (They are. I welcome search engines and AI answer engines, because that's how people find a free tool.)

Clue 2: the "browsers" aren't browsers

Cloudflare groups page views by browser. The biggest group isn't Chrome:

Browser Page views
Unknown / Others 30,940
Chrome 10,130
Mobile Safari 3,780
Googlebot 1,890
Bingbot 1,090
Edge 970
Firefox 630

The top two "visitors" are crawlers

"Unknown" is three times bigger than Chrome. The user-agent list explains it:

  • Petalbot (Huawei's search crawler): 10,290 page views. My single biggest "visitor".
  • SemrushBot (an SEO tool): 5,150.
  • An iPhone on iOS 13, from 2019, with 2,760 page views. That exact user agent is a favourite of scrapers.
  • msh-pdfmin/1: 2,720. I have no idea what this is. It reads PDFs, apparently.
  • ElevenLabs' crawler, Amazonbot, Googlebot and Bingbot after that.

Across all requests, 11,790 had no user agent at all, and 14,700 came from Headless Chrome: Chrome with no screen, which is what scrapers and test bots run.

Clue 3: Singapore loves my site (it doesn't)

Page views by country:

Country Page views
United States 22,120
Singapore 12,240
Germany 2,120
Brazil 1,730
India 1,240
France 1,150
Finland 1,120

Page views by country

Singapore, with six million people, sends ten times more page views than India, where I live and where most people I know use the site.

But the best clue is the ratio of requests to page views, because a real visit pulls in fonts, scripts and, if you use a tool, its engine:

Country Requests Page views Requests per page
India 27,400 1,240 22
United States 73,110 22,120 3.3
Singapore 18,910 12,240 1.5

Indian visitors load 22 files per page: they're opening tools and using them. "Singapore" loads one and a half: fetch the HTML, leave. That's what a crawler in a data centre does, and Singapore is full of data centres.

Clue 4: bots looking for a server that doesn't exist

Fizzdoc has no backend. No database, no login, no API, no WordPress. Bots don't know that, so they keep checking:

Path Blocked requests
/api/ 60
/wp-config.php 40
/wp-config.php.old 40
/settings 40
/login 20
/wp-config.php.bak 20

Bots looking for WordPress passwords

wp-config.php is where WordPress keeps its database password, and .old and .bak are the backup copies people forget to delete. Someone is checking my static site for all three.

Add the rest of the guessing and 30,650 requests got a 4xx error: one in nine requests was for something that never existed.

My favourite part of having no backend: none of this matters. There's nothing to find.

So how many people? Count the downloads, not the visits

Page views are easy to fake. Downloading an AI model is not, and bots don't bother, because they never run the tool.

Fizzdoc's AI tools download a model the first time you use them, and the browser keeps it afterwards. So the bandwidth spent on model files is a rough count of real people who actually used an AI tool:

Where the 18 GB went: model files, not pages

  • Phone background removal. The model is 4.57 MB and served 1.19 GB: about 260 downloads.
  • Desktop background removal. The model is about 100 MB, split into 20 MB parts because Cloudflare Pages won't host a file over 25 MB. Each part served 1 to 1.7 GB: roughly 50 to 80 full downloads.
  • Speech to text. The encoder is 10 MB and served 608 MB: about 60 transcriptions.

These are rough. A download that stops halfway still counts, and one person on two devices counts twice. But the scale is clear: tens of thousands of "visits" in the dashboard, a few hundred real uses of the heavy tools. Add the people using light tools that need no model (compress, merge, convert) and the real number of people is in the low thousands, close to the 17,490 "unique visitors" at best, and nowhere near 36,000 visits.

Honestly? A few thousand people a month trusting a side project with their files is more than I expected. I just wanted the real number.

Bonus: my CDN wasn't caching my heaviest files

One more number bothered me: only 41.65% of requests came from Cloudflare's cache, and only 11.82% of the bandwidth (2.15 GB of 18.23 GB).

Cache hit rate

For a site that is 100% static files, that's low. The headers explain it:

$ curl -sI https://fizzdoc.com/assets/index-....js
cache-control: public, max-age=31536000, immutable      <- cached for a year

$ curl -sI https://fizzdoc.com/bg/birefnet-lite/model.onnx.part0
cache-control: public, max-age=0, must-revalidate       <- re-checked every time
cf-cache-status: DYNAMIC
Enter fullscreen mode Exit fullscreen mode

The JavaScript is cached perfectly. Its filename contains a hash of its contents, so it can be cached "forever". But the model files, the heaviest files on the site, had no cache rule, so 16 GB went out uncached.

Visitors barely noticed, because the browser keeps a model after the first download. But it's the wrong default, and it's a few lines in a _headers file. That's the next post: the fix, and the before and after.

What I learned

  1. Dashboard visits are not people. On a small site, bots can easily be most of the traffic. Don't put "36k visits" on a slide.
  2. Your top paths are a free bot report. robots.txt, wp-config.php and 404s tell you who's knocking.
  3. Requests per page view is a great bot detector. People load assets; crawlers fetch HTML and leave.
  4. Count something bots can't fake. For me it's model downloads. For you it might be a form submit, an export or a signup.
  5. You don't need tracking to learn a lot. Aggregate server-side numbers answered most of my questions without a single cookie.
  6. Check your cache headers. A static site with a 42% cache hit rate has a config bug, not a traffic problem.

Fizzdoc is free and open source. It runs 74 PDF, photo and audio tools entirely in your browser, in 17 languages.

If you've been through your own logs and found something weird, tell me in the comments. And if you think my real-human estimate is too high or too low, I'd love to know how you'd measure it.


Originally published on my blog.

Top comments (0)