8. Cookie-Free Analytics

A F1 formula racecar model in cutaway

Goal

The JAMStart goal is to have an inexpensive telemetry solution to send site usage information, page views, and call to action clicks without using Personally Identifiable Information (PII). I want the data to help me understand what my readers/users were engaging with on my site and help me develop more useful content and software for them. I also didn't want to bother them with a cookie banner, complex privacy statement, or sharing my site user usage to a third party. In the end I decided to use the admirable CloudFlare Web Analytics.

The video Checking Out Free Web Analytics Service from CloudFlare Video by NetSec, while a bit old (3 years when I viewed it), helps understand the tradeoffs with CloudFlare.

CloudFlare Analytics

  1. First Create an account at CloudFlare
  2. Add your site at CloudFlare Web Analytics
  3. Get the HTML snippet for your site.
<!-- Cloudflare Web Analytics -->
 <script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}'></script><!-- End Cloudflare Web Analytics -->
  1. Configure Account Token in Nuxt Module

JAMStart already has installed the CloudFlare Analytics Nuxt Module with a general command

npm i nuxt-cloudflare-analytics

Replace the XXXXXXXXXXXXXXXXXXXXXXXXXXXXX value next to the token key with your CloudFlare account token in your nuxt.config.ts file

export default defineNuxtConfig({
  modules: [
    'nuxt-cloudflare-analytics'
  ],

  cloudflareAnalytics: {
    token: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
  }
})

Alternately Use HTML Snippet directly

JAMStart has already included the CloudFlare Nuxt module, but I wanted to note that you can use the html snippet from CloudFlare by directly in the app.vue <template> section. If you do that you can remove the CloudFlare Nuxt module.

npm uninstall nuxt-cloudflare-analytics

Remove the CloudFlare module from nuxt.config.ts file

  1. Remove nuxt-cloudflare-analytics from the modules list
  2. Remove the cloudflareAnalytics key and value.

CloudFlare Analytics testing

I was unable to get it to test from local host, so testing from a web host was the only option for testing if web analytic events were flowing.

CloudFlare Dashboard

Sample Data

With the Nuxt CloudFlare module installed and the proper token registered, you site will get both Web Vitals and Page Views automatically.

Dashboard Page Views

CloudFlare Analytics Dashboard - Page View Graphs

Dashboard Vitals

CloudFlare Analytics Dashboard - Web Vitals

Why Not Google Analytics?

I initially started with Google Analytics (GA) as I had used their web analytics in the past. I was hopeful after watching the Tim Benniks removing google analytics video that I could skip the whole cookie thing. Unfortunately, with the GDPR and CPPA regulations and GA with its V4 reimplementation, I could not avoid cookies. Below are notes on how I implemented GA as I got fairly complete, but I'm only providing for historical information for developers who might want to implement GA in JAMStart...

My GA approach was:

  1. Register a Google Analytics Custom Measurement Protocol API Secret
  2. Create a custom Nuxt Client-only Plugin for analytics (since I would use this on a static generated site)
  3. Use the plugin to allow sending data to google analytics Custom Measurement Protocol
  • On plugin startup (setup) generate or use local storage to generate a unique User ID but not Identifiable.
  1. Add a router.afterEach hook to send analytics for each page opened.

Google Analytics API Secret

The creation of the Google Analytics stream, Measurement ID and Protocol API Secret was pretty straightforward and free.

Client only Nuxt Plugin

Next I learned on how to create client-only Nuxt Plugin using the. Nuxt plugin guide. Using .client suffix name fragment was what was needed to create a client only Nuxt Plugin ./plugins/analytics.client.js. Here is the analytics plugin that I created.

// Create a session variable for the userId
let userId = 'devUser';

// Create a send function 
function send(data) {

  let metric = new URLSearchParams({
    ... {
      v: '1',
      tid: 'G-XXXXXXXXXX',
      cid: userId,
      dl: location.href,
      ua: navigator.userAgent,
      dr: document.referrer || '',
      sr: `${screen.width}x${screen.height}`,
      vp: `${document.documentElement.clientWidth}x${document.documentElement.clientHeight}`,
      sd: `${screen.pixelDepth}-bits`,
      ul: navigator.language,
    },
    ...data
  }).toString();

  // This didn't work to use sendBeacon
    // navigator.sendBeacon(
  //   'https://google-analytics.com/collect',
  //   metric
  // )

  // https://developers.google.com/analytics/devguides/collection/protocol/ga4/sending-events?client_type=gtag
  const measurement_id = `G-XXXXXXXXXX`;
  const api_secret = `XXXXXXXXXXXXXXXXX`;

  // This didn't work either (received 204 responses)
  // You have to run the ga.js first and introduce all the PII tracking and cookies for this to work.
  fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${measurement_id}&api_secret=${api_secret}`, {
    method: "POST",
    body: JSON.stringify({
      client_id: userId,
      events: [{
        name: 'tutorial_begin',
        params: metric,
      }]
    })
  });
}

// This is the function randomly generate a userID and store it in local session.
function generateUserId() {

  if(process.env.NODE_ENV === "production") {
    if (!localStorage.getItem('ppAutoUserId')) {
      // Generate a random unique ID if it doesn't exist
      const uniqueId = Date.now().toString(36) + Math.random().toString(36).substr(2);
      localStorage.setItem('ppAutoUserId', uniqueId);
    }
    return localStorage.getItem('ppAutoUserId');
  } else {
    return 'devUser'
  }
}

// This is the magic of the plugin.  I set up the userId and an afterEach page view in the router to send a 'pageview' event.
export default defineNuxtPlugin(() => {
  
  userId = generateUserId()

  const router = useRouter();
  router.afterEach(() => {
    send({ t: 'pageview' })
  })
})

Google Analytics - Not Used

In order for that code to work, you have to run the GA Tag Manager js script which creates the cookie which requires the cookie banner and privacy policy and turning user data over to Google. This also can get blocked by ad blockers. I decided against using Google Analytics until they can provide a better solution for my scenario.

Summary

When your goal is free, simplicity, and without cookies, one can use CloudFlare Web Analytics. If you have other goals (like ads, etc.) other solution stacks, like Google Analytics, need to be implemented.