Loop Code Recur All Articles
Algorithms & Problem Solving

Simple Site Analytics With Serverless Clojure: Track Visitors Without the Bloat

By Loop Code Recur Algorithms & Problem Solving
Simple Site Analytics With Serverless Clojure: Track Visitors Without the Bloat

Let's be honest: most personal sites and side projects don't need Google Analytics-level telemetry. You just want to know how many people showed up, what pages they hit, and maybe where they came from. But wiring up a full analytics stack for a blog you update twice a month feels like renting a warehouse to store a shoebox.

Here's a fun alternative: build it yourself with serverless functions and Clojure. It's lighter, cheaper (basically free at low traffic), and — this is Loop Code Recur, after all — it's a great way to practice thinking in iterations and data transformations.

Why Clojure and Serverless Play Well Together

Clojure's whole vibe is "transform immutable data through a pipeline," which is basically what a serverless function does: take an event in, push it through some logic, write a result somewhere. AWS Lambda, Cloudflare Workers (via a bit of GraalVM trickery), or Azure Functions can all host compiled Clojure, but the friendliest starting point is AWS Lambda with a plain JVM runtime or GraalVM native image.

The mental model:

  1. A tiny JavaScript snippet on your site fires a request to an API endpoint.
  2. That endpoint is a Lambda function written in Clojure.
  3. The function parses the request, extracts the useful bits, and appends a record to DynamoDB (or S3, if you want dead-simple storage).
  4. You query the data later with... more Clojure. Obviously.

The Analytics Function: A Pipeline in Disguise

At its heart, your tracking endpoint is one big data transformation. Here's a simplified handler:

(defn handler [event]
  (let [params   (-> event :queryStringParameters)
        record   {:path     (get params "path" "/")
                  :referrer (get params "ref" "direct")
                  :ts       (System/currentTimeMillis)}]
    (dynamo/put-item "pageviews" record)
    {:statusCode 204 :body ""}))

That's it. No framework, no middleware stack. A visitor hits /pixel.gif?path=/blog/recursion&ref=twitter, and you store one immutable map. Every request is an iteration in your data — a loop you can walk back through later.

The Client-Side Snippet

Drop this on your pages:

<script>
  fetch('https://api.yoursite.io/track?path='
    + encodeURIComponent(location.pathname)
    + '&ref=' + encodeURIComponent(document.referrer),
    {mode: 'no-cors'});
</script>

No cookies, no fingerprinting, no third-party scripts shipping half of Silicon Valley to your visitors. US readers especially — post-CCPA and with privacy fatigue at an all-time high — will appreciate a tracker that respects them.

Querying: Where the Recursion Kicks In

Here's the part that makes this a genuinely good algorithm exercise. Once you've got a pile of pageview records, you'll want rollups: pageviews per day, top referrers, unique-ish visitors.

A daily rollup is a classic group-by + reduce problem:

(defn views-per-day [records]
  (->> records
       (group-by #(-> % :ts (java.time.Instant/ofEpochMilli)
                     (.atZone (java.time.ZoneId/of "America/New_York"))
                     (.toLocalDate)))
       (map (fn [[day recs]] [day (count recs)]))
       (sort-by first)))

If you want to get fancier, counting unique visitors across days (a HyperLogLog sketch) is a genuinely interesting algorithm to implement — approximate counting that trades perfect accuracy for constant memory. That's the kind of problem this site lives for.

Costs and Gotchas

Iterate On It

Start with the minimal version above, then iterate: add a dashboard page that queries DynamoDB, add session reconstruction (grouping pageviews by IP hash within a time window — a nice sliding-window algorithm problem), or add hourly granularity.

The best part? Every improvement is just another transformation over the same immutable data. That's the Clojure way, and honestly, that's the Loop Code Recur way: one iteration at a time, until your little analytics pipeline is quietly handling thousands of events a day without you thinking about it.

Ship it, watch your visitor count go from 0 to... well, let's not set expectations. But it'll be your 0.