The blog of Blog of Christian Bär

Minimal cookieless web analytics

Annoyed by the cookie consent dialog all over the internet, I decided not to use cookies on my blog. This of course prevents me from using most of the common web analytics platforms, so I’m rolling my own.


  • View the daily numbers of blog visitors, page views and sessions.
  • No cookies but still identify unique visitors.
  • Cheap or free.

The solution

To address the goal “cheap or free” I’m using Azure Functions with its consumption based billing model for tracking page hits. At the time of writing this article 1 million function executions per month are free. However, some charges apply for the storage of the functions themselves.

With the decision made to use Azure Functions, it was an easy one to also store the actual tracking data on Azure, namely on Azure Table Storage, a very cost effective and simple NoSQL storage offering.

To identify unique visitors while not using cookies I’m using fingerprintjs2 to get a browser fingerprint.

To view the tracked data I use a simple React-based static website hosted for free on Netlify. It displays data returned by some Azure Functions HTTP endpoints which query the Azure Table Storage table.

Azure Functions

The following Azure Functions power the tracking of the page hits as well as the HTTP endpoints that return aggregated analytics data. The functions are implemented in JavaScript using Visual Studio Code.

The code can be found at


The Track function takes a JSON object and stores it in the Azure Table Storage Hit table using Azure Functions' bindings feature. Among others, the JSON object has the following important properties:

  • Page URL
  • Timestamp
  • Browser fingerprint


The PageViews function takes startUtc and endUtc parameters from the query string and returns an array of aggregated page hits such as

  {"url":"http://mypage1.html", "pageViews": 5},
  {"url":"http://mypage22.html", "pageViews": 3}

Analytics client UI

The analytics client UI is a simple React-based application hosted on Netlify at

The user can provide the base URL of the Azure Functions App which the client will use to query its data. The base URL is stored in the browser’s LocalStorage so doesn’t have to be entered everytime the client is used.

The client is implemented as Progressive Web Application (PWA) and therefore can be installed on a phone’s home screen.

The code can be found at

Notable Azure Functions configuration

Besides the - quite straight forward - configuration of most of the Azure Functions app, CORS has to be configured to enable the web site that is being tracked to call the Track function as well as to enable the Analytics client UI to call the PageViews function.

To configure CORS, select your Function App in the Azure portal, click Platform features and, under API, click on CORS.

In the ALLOWED ORIGINS list, enter the base URL of the web site being tracked as well as the base URL of the Analytics client UI. While you’re developing locally it’s also a good idea to enter your local development webserver’s URL (e.g.

The base URL is an URL including protocol, top level domain, subdomain and port. So if the website you want to track is available via https as well as http, you have to enter both URLs. Don’t enter anything that comes after the first forward slash.

Hire me! Web frontend with JavaScript, TypeScript, React, Svelte, HTML, CSS. Backend with .Net/C#, Node.js, (MS-)SQL. I fill your resource gaps, take on whole projects and create prototypes. Yes, tell me more

Share this post!