2026.06.06 (ν† )

✨ GPT-5.5 Summary γ€€

A record of attaching a free-tier Cloudflare Worker and D1 to a static GitHub Pages blog, preserving the existing GA cumulative values while showing today’s visits and per-post view counts.

I wanted to add a visitor counter to the blog.

Not because the feature itself is impressive. Actually, the opposite. It feels like such an obvious feature, but it does not naturally exist on a static blog made with GitHub Pages.

Google Analytics was already attached. As the operator, I could open GA and see visitor counts and page views. But those numbers were not visible inside the blog. Visitors could not tell whether this blog was still being read today or how much recent posts had been read.

I wanted the small feeling of Naver Blog’s Today, Total, and per-post view count indicators. It was less about vanity numbers and closer to a signal that the blog is not a dead static page, but something still being read.

So the problem was simple.

Could I preserve the advantages of a free static blog while attaching just a tiny dynamic piece for visitor statistics?

I Started by Setting the Conditions

I did not want the structure to grow just to add one visitor counter.

From the beginning, I set several conditions.

  • Keep the GitHub Pages and Jekyll writing flow.
  • Do not use a paid server.
  • Solve it within the free tier, whether Cloudflare or Firebase.
  • Attach it small, near the profile.
  • Make sure it does not become oddly long on mobile.
  • Show not only whole-blog stats, but also per-post view counts.
  • Do not throw away the existing GA cumulative values.

The last condition was especially important.

If I added a new counter and the views started from 0, it would look strange. The blog was already running, and GA already had accumulated views. But continuing to read only from GA was far from the counter feeling I wanted.

I Thought Reading GA Would Be Enough

At first, I thought reading the Google Analytics API would solve it.

GA already had the data, so I thought a Cloudflare Worker could call the GA API and return numbers. In reality, I created a service account, attached permissions to the GA property, and got API calls working.

But once I attached it, it was not what I wanted.

GA is an analytics tool. It is good for operators to look at trends later, but it felt awkward as a visitor counter that responds directly on the blog screen. The Today number was especially problematic. If I just entered the blog and today’s value still showed 0, visitors would simply think it was broken.

The metric names were also confusing. Active users and page views are different values. If I define visitor count strictly, the number looks too conservative. If I use page views, it feels more natural, but the label needs care.

In the end, the judgment was simple.

Use GA as the historical baseline, and count the increases from now on myself.

I Split Baseline and Increment

The final structure became this.

Public view count = GA baseline + Cloudflare D1 increment

The GA baseline is the already accumulated number. I choose a baseline date and store whole-site views and per-post views up to that point.

From then on, a Cloudflare Worker counts directly. When a visitor enters the blog, JavaScript calls the Worker, and the Worker records today’s/monthly/total/page-path increments in D1.

Summarized:

Existing cumulative values: Google Analytics baseline
New increments: Cloudflare Worker + D1
Public API: Cloudflare Worker
Blog display: Jekyll include + JavaScript

I liked this method because it does not throw either side away.

It preserves existing GA data. At the same time, future counts respond directly on the blog screen.

Why Cloudflare Worker and D1?

Firebase was also a candidate. But for a feature of this size, Cloudflare was simpler.

GitHub Pages is a static site, so there is nowhere to place server code. A Worker fills that empty space with a small piece. D1 is SQLite-based, so it is enough for storing simple numbers like visitor counts.

The cost condition also fit. A personal blog visitor counter can be handled within the free tier. If a sudden crowd appears, some counting may become slightly imprecise, but the posts themselves will not break. This is not a payment system or financial ledger in the first place.

What I wanted was not exact accounting. I wanted to show the flow that the blog is being read.

I Blocked Bots and Duplicate Visits Moderately

If a counter is attached to a public page, bots can also increase the numbers.

It is hard to block them perfectly. And if I block too strictly, it moves away from the counter feeling I wanted. I preferred something that counts a little generously, like Naver Blog.

So the Worker handles only a few things.

  • Exclude obvious bot user-agents.
  • Do not count the same visitor and same page combination again within a certain time window.
  • Accumulate per-post views in the same way, based on path.

In other words, I matched it to a public visitor counter rather than a strict analytics tool.

I Attached It Small on the Screen

At first, I tried to show statistics in several cells. But UI near the profile has to stay small. If too many numbers appear, the blog’s first screen becomes messy.

So I eventually kept three values.

Today / Month / Total

Today gives the feeling that the blog is being read now. Month shows recent scale. Total shows the time the blog has accumulated.

In post lists, I added view counts to each post.

10 views

This value uses the same structure as the whole-site stats.

Per-post views = GA per-post baseline + Cloudflare D1 per-post increment

For example, if a post had 9 views based on GA and was read once more after switching to the new counter, the screen shows 10 views.

That felt the most natural. Past views are not discarded, and future views accumulate immediately.

Result

As a result, numbers like these appeared near the blog profile.

Today 1 / Month 66 / Total 4,944

And post lists show view counts like this.

10 views

If I look only at the feature, it is small. But I like this work quite a bit.

The problem was clear. A GitHub Pages blog did not have a visitor counter. GA existed, but it was not enough as a public-screen counter. Still, I did not want to build a large server just for one blog.

So I preserved existing GA data as a baseline and attached a structure that counts only later increments with Cloudflare Worker and D1.

The advantages of the static blog stayed intact. I write in Markdown, deploy with GitHub Pages, and maintenance cost stays close to zero. I only placed a small serverless piece on top of the exact missing part.

If I later show this blog like a portfolio, work like this may be easier to explain than it looks. It is not a grand feature, but it started from a real inconvenience, constrained cost and complexity, and was finished in an operable form.

Leave a comment