It turns out that my Jekyll-based static site was storing cookies, of which I wasn’t aware. Oops! Sorry! Cookie opt-in is required by law in Germany and I’ve now implemented this. Here are the gory details of how I set it up.
While working on a completely separate project, I happened to stumble on the fact that Google Analytics cookies were being saved in my browser. Suddenly a penny dropped and I realised that even my static blog must be saving cookies. Doh! It’s actually quite embarrassing to have to admit that, but it honestly never occurred to me.
Implementing cookie consent with Minimal Mistakes
Although turning Google Analytics on is very easy in the Jekyll theme that I use (Minimal Mistakes); it turns out it’s not so easy to turn it off, at least, not when one wants the cookie state to depend upon the wishes of the user visiting the site. Nevertheless, I’ve managed to implement opt-in of cookie consent and I thought it might be interesting (especially for other Jekyll users) to show how I did it.
Check the site for cookie consent violations
A good idea starting out is to have a tool which can check for violations of the issue you’re trying to fix. This is sort of like writing a test before writing the production code. I found Cookiebot to be quite useful in checking my site for GDPR compliance. It’s possible to specify your website address and your email address (so that they can send you the compliance report), and then within about 10–20 minutes you’ll get an email with a report of the GDPR issues on your website. You won’t get a complete report if you don’t register for the service, which is free to use for monthly checks of your site. After using the service for the initial check of my site, I finally succumbed and registered so that I could make a more thorough check of my site, which will hopefully reduce such problems in the future.
The first check showed 7 issues on my site: 6 of which were related to Google Analytics; the remaining one was from Disqus (a website comment platform). Here’s an excerpt from the first report (it’s in German, possibly because Cookiebot noticed my location and the location of where my site runs from):
The report is quite detailed and tells you the name of a cookie, where it’s used on the site, where it comes from, and what the cookie’s description is. It also tells you if the country the data is sent to is adequate for the terms of the GDPR. All of this information is very handy when trying to work out if a site is GDPR-conformant.
Give users the chance to opt-in to cookies
A recent legal decision by the German Federal High Court confirmed an earlier decision by the European High Court that users must explicitly opt-in for cookies in order for the website to use such things as tracking information. More information than you probably want to know is available in the article BGH-Urteil: Opt-In-Pflicht für Werbe- und Marketing-Cookies (FAQ mit Anleitung und Checkliste).
The Minimal Mistakes author recommends using Cookie Consent by Osano (a data privacy platform to aid sites become compliant with data protection laws); Cookie Consent is also an Open Source project on GitHub, thus one can peruse the code if so inclined.
I decided to use the Cookie Consent service on my site; the service can be configured by visiting the Cookie Consent download page. Upon visiting the page, I chose to use Open Source Edition option; the configuration process starts after one clicks on the “Start Coding” button.
If you’re following along, you should now see the configuration wizard appear, which will look something like this:
In my case many of the default options were appropriate for what I wanted to do, hence I filled out the form elements like so:
- Position: banner bottom (the default option)
- Layout: block (the default option)
- Palette: black with yellow “accept” button (the default option); this also looked to fit the colour scheme used on my blog site
- Compliance type: due to the legal requirement for users to opt-in to cookies, it was necessary to select the “Ask users to opt into cookies” option. The configuration wizard is very helpful here as it then tells you that your site needs to be modified for this option to work and advises you to read the disabling cookies documentation.
- Custom text: here I simplified the default message a bit so that it is more specific to my site.
Once the form is filled out, the “Copy the code” section on the right
contains the CSS code which needs to be added to the
<head> section of
<body> of all pages on your site. In my case, this was:
The next question that we’re confronted with is: where do we put this code within a Jekyll site? And more particularly: is there anything we have to do specifically for the theme that we’re using?
A diversion into Jekyll-land
A Jekyll-based site contains several directories with different functions.
_pages are for normal pages,
_posts are for blog posts,
_drafts are for draft posts. The style and layout of the site is
usually controlled by a theme of some description; layout information is
specified within the
_layouts directory, various extra files which need to
be included are in the
_includes directory and various data files are
_data. To change the theme’s behaviour slightly, one copies the
relevant file or files from the theme into the equivalent directory
structure in one’s project and then changes the file appropriately. This
then overrides the original behaviour and–depending upon the theme–is
usually quite a small change thus allowing easy extension of a theme while
minimising the risk of breaking anything when upgrading the theme or Jekyll
In my particular installation of Minimal Mistakes, I’d installed the Ruby gem version of the theme, hence it’s fairly easy to find out where the theme files have been installed on the filesystem. Just run:
$ bundle info minimal-mistakes-jekyll
from the base directory of your Jekyll site (the directory containing the
bundle info command gives output similar to this:
* minimal-mistakes-jekyll (4.19.1) Summary: A flexible two-column Jekyll theme. Homepage: https://github.com/mmistakes/minimal-mistakes Path: /home/<username>/.rvm/gems/ruby-2.5.3/gems/minimal-mistakes-jekyll-4.19.1
The contents of this directory are:
assets/ CHANGELOG.md _data/ _includes/ _layouts/ LICENSE README.md _sass/
To solve the current problem, we’re interested mostly in the contents of the
_includes/ directory, however there are more things that one could tinker
with if one wanted to and that’s outlined nicely in the Minimal Mistakes
documentation for Overriding Theme
Setting up the CSS
Now that we know where everything is, we can start to look for files to override.
It is customary to customise a
custom.html file. To add the required
Cookie Consent CSS code to the
<head> section of the document, we simply
custom.html file from the theme’s
into the local project’s
_includes/head/ directory, which we need to
$ mkdir -p _includes/head $ cp /path/to/gems/minimal-mistakes-jekyll-4.19.1/_includes/head/custom.html _includes/head
In the default Minimal Mistakes setup, this file only contains comments:
Pasting the CSS code from the Cookie Consent site in between the start and end comments, we get this:
Note that it’s split into two separate chunks:
- the Cookie Consent code loaded from a CDN.
- the initialisation code required for the local site.
What we’ll need to do is to put the Cookie Consent CDN-based code into a
custom footer, then put the local initialisation code into a local
assets/js/ which we then include by adding it to the
list of scripts in the
after_footer_scripts value in the Jekyll
_config.yml file. Thereafter, we’re going to have to override (and
encapsulate) the included the Google Analytics and Disqus code so that we
can activate or deactivate it according to the user’s cookie wishes. That,
at least, is the plan.
What you might be wondering right now is: “Hang on, how did he work that out? How was that even obvious?” It wasn’t. This is the third or fourth (or so) time that I’ve been through this process (and this post) in order to get the implementation right. I considered showing all of my wild goose chases, but then decided it was a much better idea to just introduce the “happy path” as that would cause much less confusion for anyone who was brave enough to follow the plan I outline here.
If you’re wondering how I came up with the
the reason is this: the custom footer code is included before any
analytics-provider or comments-provider code is included, however we need to
put the Cookie Consent initialisation code after the analytics and
comments code so that we can dynamically enable/disable it. Therefore,
putting all of the Cookie Consent code into the custom footer is
insufficient. Note that this is slightly different to the recommendations
put forward by the Minimal Mistakes
where he only mentions the custom footer step. My guess is that given the
opt-in requirement that we have, the final solution ends up being more
First, let’s put the Cookie Consent CDN link into the custom header. We
need to create the
$ mkdir -p _includes/footer
then copy the default custom footer file into this newly-created directory
$ cp /path/to/gems/minimal-mistakes-jekyll-4.19.1/_includes/footer/custom.html _includes/footer/
and finally copy the Cookie Consent CDN script link into this file, which will now look like this:
Now we create the file containing the Cookie Consent initialisation code.
$ mkdir -p assets/js
and create a file in this directory called
cookie-consent.js with the
initialisation code mentioned above:
To ensure that this code is included in our site, we need to set the
after_footer_scripts variable in the base
_config.yml file. This
variable doesn’t exist by default, so I added it to the end of the
Settings section of my config file. Adding the following snippet is
therefore sufficient to include the Cookie Consent initialisation code (see
Note that this only sets the stage for the remaining steps necessary to implement cookie opt-in on our site. To get the ball rolling, let’s implement opt-in for the Google Analytics tracking stuff.
Opting-in to Google Analytics tracking
Having a look through the code for the Minimal Mistakes theme, I found that
the Google Analytics code is included as part of the general “analytics”
include file, which itself is included as part of
scripts.html, which is
part of the default layout in
_layouts/default.html. A diagram makes this
structure a bit clearer.
_layouts/default.html -> _includes/scripts.html -> _includes/analytics.html -> _includes/analytics-providers/google.html
The content of
google.html looks like this:
and means that the code (and hence the tracking) will be active whenever this code is included.
So how can we allow the user to toggle the activity of this code? One solution I found wrapped the Google Analytics code in a function which was then activated depending upon the user’s choice1. The user’s opt-in choice then calls this function via the Cookie Consent cookie toggle callback hook. This seemed like a good idea and hence I decided to copy the idea for my site.
The plan to ensure we only call the Google Analytics code when the user has accepted cookies then looks like this:
google.htmlwith a version that wraps the tracker activation code in a function.
- Extend the Cookie Consent initialisation code in
cookie-consent.jswith the Cookie Consent toggle callback hook code.
- Call the newly-wrapped function within the relevant section of the callback hook code.
Ok let’s do this. Create a directory in the local project matching the path
_includes directory in the Minimal Mistakes theme:
$ mkdir -p _includes/analytics-providers
and copy the theme’s
google.html into the new directory:
$ cp /path/to/gems/minimal-mistakes-jekyll-4.19.1/_includes/analytics-providers/google.html _includes/analytics-providers
google.html file currently looks like this:
Now we wrap this entire block of code in a function called
loadGAonConsent(). Note that it was necessary to explicitly set the
_gaq variable as a property on the
window object (as it would have been
in the previous scope) so that the tracking code works as normal. The
google.html file now looks like this:
This code will be included in the HTML provided to the user, however it
won’t be active. To be able to make this active, we add the Cookie
Consent callback hook
to the object passed into the
Pasting this code into
cookie-consent.js and adding
if blocks marked with
// enable cookies (and hence following the
advice in step 6 of DSGVO: Google Analytics mit Opt-In
Rolling this out to production2 allows us to see if this all works. First, I cleared the cookies for my site3 and checked in the “Storage” tab of the developer tools to ensure that the cookies were, in fact, gone.
As we can see, there aren’t any cookies on the
ptc-it.de domain, which was
the goal in this step. Also, we see that the cookie opt-in/opt-out bar
appears automatically and that no cookies are saved before this is allowed
Note, however, that a cookie slot for
disqus.com appears in the list of
cookies. As we will see below, this cookie is empty, however we still get
problems with its existence.
Clicking on the “Allow cookies” button we see that the Google Analytics
cookies are set (the
__utm* cookies are the Google Analytics cookies):
In an ironic twist, it turns out that Cookie Consent also saves a cookie in order to save the cookie consent status. Although this seems somewhat odd, I think this makes sense and is probably the only straightforward solution for the library to implement.
Deleting the cookies
again and this time selecting “Decline”, we see that only the Cookie Consent
status cookie is set and that its value is
deny as we would expect:
I also checked the behaviour in the Google Analytics real time report while running the above tests and was able to verify that with cookies denied, no usage was registered, whereas with cookies allowed, there was activity. This is the behaviour one would expect.
The local tests we can perform are effectively exhausted at this point, hence it’s a good idea to check via a third party. I ended up signing up to Cookiebot so that they would automatically check my site once a month and so that they’d check more than a single page (as is done in the semi-anonymous compliance check I made initially). We see now that the scan report shows only the one necessary cookie:
Although this is a good sign, I decided to try the initial compliance check again, just to see what its output is. After entering the website address as well as my email address, confirming my email address and waiting the necessary 10–20 minutes for the report to be generated, I got this output (truncated for brevity):
which shows that there are Disqus cookies being set without the user’s consent. This is … weird. It’s weird because this output is at odds with the supposedly more thorough check made for subscribed users. What’s also weird is that when the cookies are cleared, there is a slot set for the Disqus cookies, but the cookie is empty:
I admit to being confused about this state, however I’m making the guess that Cookiebot checks my site more thoroughly for GDPR compliance than I can (even if there are differences in the subscribed and unsubscribed reports). If anyone can explain these results to me, I’d be really happy to know what’s going on!
All this means is that we also have to deactivate the Disqus cookies, which we thought was necessary anyway. So let’s do that.
Opting-in to Disqus cookies
We now just need to follow the pattern outlined in the previous section: we
function and call this function only if the user has opted-in for cookies.
We start by creating an
_includes directory for comments providers include
files as is the pattern in the Minimal Mistakes theme
$ mkdir -p _includes/comments-providers
and copying the
disqus.html file into this directory
$ cp /path/to/gems/minimal-mistakes-jekyll-4.19.1/_includes/comments-providers/disqus.html _includes/comments-providers
disqus.html file currently looks like this:
Now we just need to add this function after
Rolling this out to production and running the tests mentioned in the previous section showed that the Disqus cookie slot is no longer set when cookies have been deleted and the page reloaded:
This is a good sign. However, what does the Cookiebot compliance report say? I requested the basic report for unsubscribed users again and got this result:
Now that’s what I like to see! There aren’t any cookies identified at all! Visitors to the site now have to explicitly opt-in to cookies as is required by law. Phew! That was a fair bit of work, but we got there in the end.
Cookie consent opt-in is a requirement of European law and websites (no matter how simple) need to ensure that they are GDPR compliant. Finding out that your site is non-compliant can be a surprise, but there is plenty of help online to ensure everything is in order to protect the data of visitors to your site.
Is there anything that I’ve missed? Was this post helpful? How could I make it better? Let me know in the comments section (you’ll need to enable cookies!) or simply drop me a line via email or ping me on Twitter.
See the section “Schritt 6: Der komplette Code” for the code that I based my solution upon. ↩
We can’t check this in the development environment because all of the analytics tracking and comments stuff is disabled; otherwise any work in the development environment would appear in the analytics output and thus give incorrect results. ↩