How I removed google analytics and still have good data to analyze

Tobias Nickel - Dec 25 '20 - - Dev Community

It was just recently, that I opened my google analytics account and added it to this website. I wanted to get some insights, about my website's visitors. But compared to the google search console, there was not much information interesting to me.

And actually I worried a little. Is it legal to just add analytics? analytics was easy to add, just adding a script tag into my page. In EU it is needed to inform the user about non essential cookies. Before setting one, it is needed to ask the users consent. However, analytics got added using a static html tag and there is no way to control what cookies are set immediately.

I was not sure if I should create that script tag dynamically after asking the user, using some client side javascript. and would analytics still work?

On the internet when searching for analytics without cookies there are many websites advising to use motomo. It is a very good solution made with php and mysql. But for my little blog setting up this server seem a little to much. Also because I would have to look that I keep it up do date and do some more security measures. For real production application, google analytics and motomo, both will be a better choice recording lots of data you don't know now you want to have in the future.

My Solution to do Analytics without Analytics

I added a little script into my website. Instead of cookies it uses local storage. local storage can not be used to track users across other websites. So I think this should comply with the law. Also in the storage there is nothing stored to identify the user.


// analytics
const lastViewTime = parseInt(localStorage.getItem('lastViewTime')) || 0;
const viewCount = parseInt(localStorage.getItem('viewCount')) || 0;
const lastViewPage = localStorage.getItem('lastViewedPage') || '';

localStorage.setItem('lastViewTime', Date.now())
localStorage.setItem('viewCount', viewCount+1)
localStorage.setItem('lastViewedPage', document.location.href);

fetch('/api/pageViews', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    page: document.location.href,
    viewCount,
    time: Date.now(),
    lastViewTime: lastViewTime,
    lastViewPage: lastViewPage,
    userLanguage: navigator.language,
    userAgent: navigator.userAgent,
    referrer: document.referrer,
    dayTime: parseInt(req.body.dayTime+''),
  })
})  
  .then( r => r.json())
  .then(data => console.log('pageViewResult:', data);

Enter fullscreen mode Exit fullscreen mode

On the server I just dump this information into a jsonl file, meaning one json log entry each line. It can easily be converted to csv for analyses via excel. Draw some charts or count per interval weekly and monthly interval.

const router = require('express').Router();
module.export.pageViewRouter = router;

const file = fs.createWriteStream(fileName, {
  flags: 'a' // 'a' means appending (old data will be preserved)
});

router.post('/api/pageViews',async (req,res) => {
  res.json(true);
  file.write(JSON.stringify({
    page: body.page,
    time: Date.now(),
    userLanguage: (req.body.userLanguage+'').substr(0,500),
    userAgent: userAgent.id,
    viewCount: parseInt(req.body.viewCount),
    lastViewTime: parseInt(req.body.lastViewTime+''),
    lastViewPage: req.body.lastViewPage,
    referrer: req.body.referrer,
    dayTime: new Date().getHours()
  })+'\n', (err)=>{
    if(err) console.log(err)
  });
});
Enter fullscreen mode Exit fullscreen mode

Do you see, that I do not check if the browser supports the fetch API and modern arrow functions? I was thinking about it, and decided that I don't need to care about old browser compatibility for this optional feature.

You see all the fields that are getting stored. These are what I came up with. That I think are interesting. To be honest, the API shown is not exactly the one running at tnickel.de, but the concept is this. On my running implementation I validate the received data, store urls and user agent string into a separate json file database and write the id into the log file. But with this example you can understand how you can implement the server side yourself.

How others do it

As by chance: The dev.to community, was just asked about analytics tools. And I described my little solution. The comment received a reply by Charanjit Chana, saying he is using a similar solution, here is what I found on his websites source code (it was minified, so I formatted it a little):

function allowedToTrack() {
  return !(window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack || window.external && "msTrackingProtectionEnabled" in window.external) || "1" != window.doNotTrack && "yes" != navigator.doNotTrack && "1" != navigator.doNotTrack && "1" != navigator.msDoNotTrack && !window.external.msTrackingProtectionEnabled()
}
if (allowedToTrack()) {
  let o = Math.floor(8999999 * Math.random()) + 1e6;
  let n = window.innerHeight + "x" + window.innerWidth; 
  // this request then set the cookie. 
  fetch("https://123.charanj.it/xyz/api/" + o + "/false/" + n);
}

if (void 0 !== console) {
  console.log("%cđź‘‹ Hey!", "font-size: 16px; font-weight: 600");
  console.log("%cIf you can see this I would love to hear from you.", "font-size: 16px;");
  console.log("%cYou can find me at https://twitter.com/cchana.", "font-size: 16px;");
  console.log("%cUse the hashtag #cchanaconsole", "font-size: 16px;");
  console.log("%c🤙 🖖", "font-size: 16px;");
}
Enter fullscreen mode Exit fullscreen mode

Seems as head of development he is interested in finding new developer talents for his team. I like the allowToTrack function used before the analytics request is made. This request then set a cookie, so multiple page views can be related to the same user and session. I don't know about the rules in England after it left the EU, but I believe in Germany, an additional popup banner would be needed. Other than me, Charanjit is interested in the users screen resolution to know what to optimize the page for.

How do you analytics on your website?

You now have seen two valid approaches to building the client side for collecting analytics information. With this article, I hope you find how how this website does analytics, without tracing the users all over the internet and even into their darkest dreams.

Update January

In a number of comments people point out, that storing identification data in local storage is under the law similar to storing it directly as a cookie.

I was thinking this would be OK, because it would mean, that you can not be tracked with it over other websites. But anyway, I did not store personal identifiers. Or did I?

I think at this point you have to really believe the website operator try to trick you. And if they really wanted, it would be easier to simply show a cookie banner and get consent.

But lets us pretend I wanted to track you personal journey on my(your) website. With the recorded information, there is the viewCount and ViewTime the current and last URL. Just like that these information can plot a journey, but are not connected to a person. However when I or any other web provider with such solution plan to connect journeys with user information that could be possible by: provide a feature or content on the page that require authentication. At the moment of authentication it would be possible to connect that user with his already journey. And that is not good.

Here is some idea, that can make it more difficult for you to connect a journey to a user, but still maintain good insights to users in general.

  1. Round the timestamps to a full minute or several minutes.
  2. Same with the viewCount. I came up with the following function. The function still allow you to know if there are regular users or just random spontanious visitors.
function normalizeViewCound(count){
  const sqrt = parseInt(Math.sqrt(count).toString())
  return sqrt * sqrt;
}
Enter fullscreen mode Exit fullscreen mode

So here is the version that I currently use for my website:


const lastViewTime = parseInt(localStorage.getItem('lastViewTime')) || 0;
const viewCount = parseInt(localStorage.getItem('viewCount')) || 0;
const lastViewPage = localStorage.getItem('lastViewedPage') || '';

const now = Date.now();
const visitTime = now - (now % 60000); // normalize the time

localStorage.setItem('lastViewTime', visitTime)
localStorage.setItem('viewCount', viewCount + 1)
localStorage.setItem('lastViewedPage', document.location.href);

function normalizeViewCound(count){
  const sqrt = parseInt(Math.sqrt(count).toString())
  return sqrt * sqrt;
}

fetch('/api/pageViews', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    page: document.location.href,
    viewCount: normalizeViewCound(viewCount),
    time: visitTime,
    lastViewTime: lastViewTime,
    lastViewPage: lastViewPage,
    userLanguage: navigator.language,
    userAgent: navigator.userAgent,
    referrer: document.referrer,
    dayTime: new Date(visitTime).getHours()
  })
}).then(function (r) {
  return r.json();
}).then(function (data) {
  console.log('pageViewResult:', data)
});
Enter fullscreen mode Exit fullscreen mode

With these changes, the privacy of my and your users is greatly improved. However, I can't really give legal advice here and know for certain if the measures are enough. Maybe it is just easier to just show the users a cookie information and shameless track them into their most private dreams.

. . . . . . . . . . . . . . . . . . . .
Terabox Video Player