Table of contents
When rebuilding my previous website, I wanted to add a way to know how many people are visiting this website and what articles are they reading. In short, a simple analytics solution. However, there are a few things that made it more complicated.
First of all, this website uses Nuxt Content, with AWS Amplify to host it as a static website. It doesn’t need to reload the entire page when you click a link, but loads a
payload.js file instead. There is also smart prefetching under the hood. This is great to make this website faster and create a smooth user experience. However, from an analytics perspective, it means I cannot rely on the access logs. This meant that I would have to use client-side code to generate my telemetry data.
Because of these problems, I decided to make a custom solution. While it’s probably not the most feature-rich solution, it does what I need it to do, and it was fun building it.
When starting this project, I had two main questions:
- How many people visit my website?
- Which are the most popular pages on my website?
Since I had to build a client-side solution, I could also add more data, such as tracking from which page do people come from when they click on a link, and how long are they staying on each page. I will probably add more as time goes, such as which country they are from, but that was good enough for now.
Since a telemetry system emits events that will be aggregated centrally, it’s important to think about the event structure beforehand. In the long-term, this will help when I want to add new fields, make changes, etc.
Each event will then contain a
eventType. They also contain three optional fields, based on the type of event:
to: the path of the page being loaded, used for
from: the path of the previous page, used for
duration: how long the user stayed on the previous page, in milliseconds, and used for
Since I’m using TypeScript, I created this interface:
// Enumeration for the types of events // Telemetry event structure
Since a telemetry event should be used on each page, I created a plugin that would trigger a function after each page load, using the global after hooks from Vue.
In a plugin file, you can add a hook like this. On the arguments, we need
app to connect to the router, and
store to retrieve and save data into the Vuex store. This hook solves the
click events, but we’ll need to do something a bit different for the
afterEach hook receives two arguments: the new page (
to) and the previous one (
from), which gives us the data we need for 2 of our fields. For the
sessionId and calculating the
duration, we’ll need to retrieve that from the Vuex store:
// Retrieve data from Vuex store ; // Compute the duration on the previous page by substracting the previous call. ;
Now we can prepare and send the event. As mentioned before, this only supports the
click events. In the first case, since this will be the first page visited,
from will contain a reference to an empty page, so we can detect if this is the first page or not by checking if
from.name is null.
For sending the event, I am making a POST call to an API using axios. I’ll write another blog post that look at the backend architecture.
// Prepare the event // We default to a start event and check afterwards for click event ; // If there is a from page, this is a click event if from.name !== null // Post it to the telemetry API '/telemetry', body;
The last thing we need to do is to commit the current time and page in the Vuex store. We’ll need to store the page path for the
// Update information in the store 'telemetry/set', ;
When a user ends their session on the website, they usually just close their tab or browser. This will not trigger any hook on the Vue Router, so we need to use something else to react to it.
For this, we can add a listener to a
beforeunload event. From there, we can retrieve information from the store and send the
end event to our telemetry api:
// On page close 'beforeunload',;
This uses the Vuex store, but we haven’t configured it yet. We are only storing three pieces of information here: the
pageTime for the timestamp of the last event and
page which will contain the path to the last visited page for the end of session event.
In this, we’ll take care of setting a random session ID using the
; ; // Default state ;
From there, we need to add a mutation that will allow setting the
// Configure ; ;
While we have created a plugin, our application is not aware of it yet. For this, we’ll need to add it into the Nuxt configuration file. Since the plugin relies on the
window object for both the end event and getting a timestamp in milliseconds, the plugin should only be used client-side.
This article only looks at the client-side of the problem. In a future article, I’ll look at how I have implemented a serverless and functionless backend to aggregate the data and generate a dashboard.