GDPR Compliance with Jekyll & Google Analytics.

Apr 26, 2020

gdpr

I’m currently in the process of setting up the infrastructure needed to run my site and at some point in the future I want to enable Google Analytics (GA) to get some usage statistics. Why Google Analytics? Well, I think it would be fun to watch my annual visitors increase (from 0 to 3 or even as much as 5) as more and more search crawler bots find and index my site…

But, it got me thinking that since GA does some work under the hood to track visitors on my site, I should probably set up a mechanism to be GDPR compliant and only track visitors if they consent to be tracked.

Cookie Consent Collection

The bulk of the work for cookie-consent collection has already been done by the Jekyll crew, so my Sunday project began by following the official Jekyll doc on the collection of cookie consent. The Jekyll team explains how their code works:

The code inserts a cookie banner at the bottom of the screen. When you click ‘Approve’ it creates a cookie that is valid for 31 days. Each page load the code checks for the existence of this cookie. If it exists (and the value is ‘true’), the blocked scripts are loaded. The blocked scripts can be found in the includes in the code below. You can easily replace these includes with your own.

and the snippet:

if(readCookie('cookie-notice-dismissed')=='true') {
    {% include ga.js %}
    {% include chatbutton.js %}
}

I went ahead and copied the cookie-consent.html into my _includes directory and then tried to find a suitable location to include it.

Working with beautiful-jekyll

I should call out that the theme I’m using is beautiful-jekyll so the specifics of the information here forward are unique to that theme, but the principles here should be applicable to other Jekyll themes–Particularly themes with out of the box support for google analytics. While I’m calling things out, I should also call out that I am seriously not a front-end engineer.

I found a document that looked like a layout document in _layouts/base.html and added the cookie-consent include beneath the footer includes shown below:

  <body>
    {% include gtm_body.html %}
    {% include nav.html %}
    {{ content }}
    {% include footer.html %}
    {% include footer-scripts.html %}
    {% include cookie-consent.html %} 
  </body>

Once I had that in place, I went ahead and fired up my local Jekyll instance to see if I was able to get a cookie-consent banner. You may have been able to guess that I encountered the following error:

Configuration file: /mnt/g/code/johnsosoka-com/_config.yml
            Source: /mnt/g/code/johnsosoka-com
       Destination: /mnt/g/code/johnsosoka-com/_site
 Incremental build: disabled. Enable with --incremental
      Generating...
  Liquid Exception: Could not locate the included file 'ga.js' in any of ["/mnt/g/code/johnsosoka-com/_includes", "/var/lib/gems/2.3.0/gems/jekyll-theme-primer-0.5.4/_includes"]. Ensure it exists in one of those directories and is not a symlink as those are not allowed in safe mode. in /_layouts/base.html

It took me a minute to figure it out–Of course I’m getting this error, I didn’t update the example includes in the _cookie-consent.html file. I just want to see if the banner is visible, so to get unstuck I comment out the ga.js and chatbutton.js includes and start my local site again.

consent banner

I see the banner below the footer, which is what I was hoping for. Next up I will test the script by clicking approve. When I click that button, I am expecting a cookie named cookie-notice-dismissed to be created with the value true. I used chrome dev tools to quickly reveal:

cookie

Wonderful! I now have the banner displaying and the cookie being properly set. When I load other pages after accepting the cookie consent policy the banner no longer displays which is perfect. Now all that remains is to:

  • Wire up OOTB GA scripts to the Cookie Consent Scripts
  • Create a cookie consent landing page
  • Update the More Info button’s href

Conditionally Loading Google Analytics

The theme beautiful-jekyll already comes with out of the box support for Google Analytics which we need to tweak so that Google Analytics is only loaded if the cookie-consent collection policy has been accepted. I know that beautiful-jekyll has OOTB GA support, but I’m not sure how it works. I began my journey by looking at the contents of my _include directory which yields:

total 48
drwxrwxrwx 1 john john 4096 Apr 26 09:46 ./
drwxrwxrwx 1 john john 4096 Apr 24 00:43 ../
-rwxrwxrwx 1 john john  182 Apr 21 17:41 comments.html*
-rwxrwxrwx 1 john john 1964 Apr 26 09:33 cookie-consent.html*
-rwxrwxrwx 1 john john  884 Apr 21 17:41 disqus.html*
-rwxrwxrwx 1 john john  302 Apr 21 17:41 ext-css.html*
-rwxrwxrwx 1 john john  271 Apr 21 17:41 ext-js.html*
-rwxrwxrwx 1 john john  712 Apr 21 17:41 fb-comment.html*
-rwxrwxrwx 1 john john 2051 Apr 21 17:41 footer.html*
-rwxrwxrwx 1 john john  370 Apr 21 17:41 footer-minimal.html*
-rwxrwxrwx 1 john john  847 Apr 21 17:41 footer-scripts.html*
-rwxrwxrwx 1 john john  615 Apr 21 17:41 google_analytics.html*
-rwxrwxrwx 1 john john  361 Apr 21 17:41 gtag.html*
-rwxrwxrwx 1 john john  292 Apr 21 17:41 gtm_body.html*
-rwxrwxrwx 1 john john  480 Apr 21 17:41 gtm_head.html*
-rwxrwxrwx 1 john john 2370 Apr 21 17:41 header.html*
-rwxrwxrwx 1 john john 4417 Apr 21 17:41 head.html*
-rwxrwxrwx 1 john john  701 Apr 21 17:41 matomo.html*
-rwxrwxrwx 1 john john 2063 Apr 21 17:41 nav.html*
-rwxrwxrwx 1 john john 1537 Apr 21 17:41 social-share.html*
-rwxrwxrwx 1 john john 1183 Apr 21 17:41 staticman-comment.html*
-rwxrwxrwx 1 john john 5559 Apr 21 17:41 staticman-comments.html*
-rwxrwxrwx 1 john john  373 Apr 21 17:41 utterances-comment.html*

I noticed google_analytics.html and decided to peak at the contents which were:

{% if site.google_analytics %}
<!-- Google Analytics -->
<script>
    (function (i, s, o, g, r, a, m) {
        i['Google AnalyticsObject'] = r; i[r] = i[r] || function () {
            (i[r].q = i[r].q || []).push(arguments)
        }, i[r].l = 1 * new Date(); a = s.createElement(o),
            m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
    })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
    ga('create', '{{ site.google_analytics }}', 'auto');
    ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
{% endif %}

So, the Google Analytics script is already being conditionally written to the document when the site is generated based off of conditions that need to be satisfied in the template–The existence of a google_analytics variable in my site configuration. I need to figure out what, in the beautiful-jekyll template, is requiring this Google Analytics script in the _include directory.

I was able to grep for “google_analytics.html” from the root directory of my project and found that _includes/head.html was the only file that would be including the Google Analytics script. In order to connect the OOTB GA behavior with the cookie-consent collection work, I should simply need to cut (remove) that include from the _includes/head.html file and paste it into the _includes/cookie-consent.html file from before–particularly where we had previously commented out the includes.

After some simple edits, my header html has no mention of GA and my cookie-consent.html has been updated to have:

    if(readCookie('cookie-notice-dismissed')=='true') {
	  {% include google_analytics.html %}
    } else {
        document.getElementById('cookie-notice').style.display = 'block';
    }

When I configure my Google Analytics account and am able to set the site.google_analytics property, GA should only load if a visitor explicitly accepts the cookie policy. To really tidy things up, I should create _/includes/consent-collected-scripts.html and have that be the only include code block where consent was granted in cookie-consent.html more closely adhering to the single responsibility principle and putting the changes we would need to make in the future into a simpler file…But, that’s not something I’m going to do today.

Now that we have Google Analytics only loading if 3rd part scripts have been approved, we are ready to move on the cookie-consent landing page work.

We are approaching the tail-end of our project today, all that remains is setting up a landing page and updating the button link on the cookie-consent banner to point to that page. This way, our site visitors can get a little more information about the intent behind the usage of Google Analytics.

Landing Page

I created a simple privacy policy explaining some of the technology used to run johnsosoka.com and what I intend to use Google Analytics tracking for and make sure it is layout: page. Now we have a resource that we can perma-link to in the consent collection banner. You could easily infer the markup by viewing the page here

Updating the “More Info” Button

If you gave your privacy policy a name other than privacy.md OR if for some reason your S3 bucket isn’t identifying index.html files inside of folders & serving them automatically you can update the link the cookie-consent.html. Mine was updated to have <a href="/privacy/index.html ... instead of <a href="/privacy... I added the ... to indicate that this is a snippet of a much larger string.

Wrap Up

At this point my site should be configured so that Google Analytics is only loaded if a user consents to the usage of 3rd party cookies & scripts.

I still need to do some reconfiguring of my Google Analytics account before making GA live on my site, but I did want to take some time and set up the infrastructure for it on the blog while I had some spare time this morning. If I flip all the switches to make this go live and it fails, luckily my site isn’t hosted in Europe :)

I will update this post when Google Analytics goes live on johnsosoka.com

[Update 4/28/20]

In going live the step I had to do in addition to the above was remove the script tags in the google_analytics.html include. Once that was resolved, everything worked perfectly. I did a quick sanity check and ensured that my session didn’t show up on the GA dashboard unless I had clicked the approve button.