Skip to content
runomine

Guide · server-side

Server-side analytics: measure from your backend, not the browser.

Almost every analytics tool loads a script in each visitor’s browser. Server-side analytics does the opposite: your application already receives every request, so it counts the visit itself.

This guide covers what changes, what you gain, what you miss, and how runomine does it.

Reviewed on October 14, 2026

What server-side analytics is

“Server-side” is used for two different things:

  • Server-side tagging: the browser still loads a script, but sends its events to an intermediate server that forwards them to Google Analytics, Meta and others. The path changes, not the origin.
  • Server-side collection: your backend records the visit as it answers, with nothing in the browser. Business events (a signup, a confirmed payment) are sent from where they happen: your code, a job, a webhook.

runomine is the second, with an optional beacon for what your backend never sees.

Backend vs browser: what each one sees

Backend vs browser: what each one sees
Library in your backend Script in the browser
What’s in the browser Nothing A script and, often, cookies
Ad blockers Can’t see what your server does Can block the script or its requests
Pages served from the CDN cache Never reach your backend: you need the beacon Counted
Navigation inside an SPA Only the first load; the rest, with the beacon Counted if the script follows pushState
Business events Where they happen: in your code, a job or a webhook Only if the browser is still on the page
Bots All of them arrive: they must be filtered (runomine does it centrally) Many don’t run JavaScript
Page weight Zero bytes; sending happens after the response One more script to download and run
The visitor’s IP Can stay on your server Reaches the vendor with every request

Counting unique visitors without cookies

Without a cookie there’s no identifier stored in the browser. runomine uses a daily hash: IP, user agent and host, with a salt derived from your token and the UTC date.

The same person on the same day gives the same hash; the next day, a different one. So you get unique visitors per day, and nobody can be followed across days or sites.

The PHP, Node, Python and Rust libraries compute it on your server and are tested against the same vector. With the beacon, runomine computes it with the same formula and discards the IP, so a person seen by both counts once.

The visitor hash
# Computed on your server, by the library
day     = "2026-10-13"                      # today, UTC
salt    = hex(HMAC-SHA256(key = token, "runomine:" + day))
visitor = hex(SHA256(salt + "|" + ip + "|" + user_agent + "|" + host))[0:16]

# Test vector every implementation reproduces
token   = "rm_live_test_0123456789abcdef"
ip      = "203.0.113.9"   host = "example.com"
visitor = 5f9e1bebe31e8bf5

# What travels to runomine: the hash, never the IP
{ "type": "pageview", "url": "https://example.com/pricing",
  "visitor": "5f9e1bebe31e8bf5", "user_agent": "Mozilla/5.0 …" }

Library, beacon, or both

  • Library

    For apps with a backend. Pageviews from a middleware and events with track(). The IP never leaves your server.

  • Beacon

    For static sites, CDN-cached pages and SPAs. Under 1.5 KB, no cookies or localStorage, and it drops visits with Do Not Track or GPC.

  • Both

    They give the same visitor hash, so nobody counts twice. The library for what goes through your backend; the beacon for the rest.

How to start on your stack

  • Laravel

    An auto-discovered middleware and Rom::track(). Laravel guide.

  • Express and Next.js

    @runomine/node: Express middleware and a helper for the Next.js middleware (App Router). Node 18+, no dependencies.

  • Django and FastAPI

    runomine for Python 3.10+: Django middleware and ASGI middleware for FastAPI or Starlette. Standard library only.

  • Rust

    A layer for axum, actix-web or any tower stack, also in blocking mode.

  • Any other backend

    POST /api/v1/events with your token: up to 500 events per call, idempotent by id.

  • Static sites

    One <script> tag with the beacon. No cookies.

Server-side doesn’t mean “outside the cookie law”. The EDPB’s Guidelines 2/2023 say the ePrivacy Directive can apply even without cookies, and the GDPR applies whenever IP addresses are processed: a hash is pseudonymisation, not anonymisation. runomine is designed to fit the audience-measurement exemption (AEPD, CNIL) for visits and funnels, and signs a data processing agreement with you (GDPR art. 28). Attributing revenue to identified users may require consent: assess it with your DPO.

FAQ

Frequently asked questions

Does server-side analytics get past ad blockers?
What your backend measures, yes: an ad blocker works in the browser and can’t see what your server does. The beacon, like any script, can be blocked.
Doesn’t it count bots too?
Your server receives every request, bots included. runomine drops them centrally (a user-agent list, headless browsers, empty user agents) and counts them per day, so the library needs to know nothing about bots.
What about pages my CDN caches?
If Cloudflare or another CDN serves the page from its cache, your backend never sees it. Add the beacon to those pages: it gives the same visitor hash, so nobody counts twice.
Is this the same as server-side Google Tag Manager?
No. Server-side tagging still starts with a script in the browser and forwards its events. runomine records the visit in your backend, with no script, and only uses the beacon where your backend can’t reach.
Does it slow my application down?
No. The libraries keep events in memory and send them in batches after the response, retry with backoff and, if they fail, fail quietly. They never throw.

Count what matters.

Visits, sales and servers in one place. Installed in less time than it took to read this page.