Add Minimal Analytics (Google Analytics v4) to Hugo

N.B. USING GA IS ILLEGAL IN SOME EUROPEAN COUNTRIES.

There are efforts made to create stripped down versions of Google Analytics version 4. This helps to speed up your site instead of loading the GA4 official library script.

However, the stripped down versions support the basic or essential tracking functions, and thus, if you require more advanced tracking (like Adwords), just use the official GA4 library instead.

Currently, I have found two implementations of the minimal GA4 script:

  1. jahilldev / minimal-analytics/ga4 - this version tracks page views, engagement time, scroll, file download and click tracking events… It weighs in at 2kb (gzipped).

  2. idarek / minimal-analytics-4-min.js - this version tracks pageviews, scroll and site search and weighs slightly over 1kb (gzipped).

This tutorial uses the code from the first implementation (the CDN section). Ensure you have the latest Hugo version (from version 0.100.0 and up).

N.B. 1 I recommend using methods 1 and 3.

N.B. 2 You might need to add https://www.google-analytics.com to your connect-src directive in Content Security Policy to make this code work (method 1 also requires using hash or nonce if you disable inline scripts in your CSP).

Method 1

  1. Create an analytics.html file inside layouts/partials and paste the code below
Summary
{{- with site.Params.google.analytics.id }}
{{- $script := resources.GetRemote "https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js" }}
<script >
window.minimalAnalytics = {
trackingId: '{{ . }}',
autoTrack: true, // <-- init tracking
};
</script>
<script src="{{ $script.RelPermalink }}" async></script>
{{- end }}
  1. Add the code below to your configuration file (this example assumes config.toml is in use) and replace G-XXXXX with your GA4 measurement ID.
[params.google.analytics]
id = "G-XXXXX"
  1. Then add the partial wherever you want it to appear in your site (e.g. inside <head></head> or just before the closing </body> tag)
{{- partial "analytics.html" . }}
  1. Check your source code (ctrl+U) to ensure your code appears correctly.

If your site is live, or even if using a test site, check the “real time” section of your GA4 property. If you implemented everything correctly, you should start seeing some live stats being recorded after a few minutes.

Method 2

  1. Copy-paste the code below in a JS file (like track.js) in assets/js folder of your base layout or your theme.
Summary
window.minimalAnalytics = {
trackingId: '{{ . }}',
autoTrack: true, // <-- init tracking
};
  1. Add this code to the analytics.html partial.
Summary
{{- with site.Params.google.analytics.id }}
{{- $script := resources.GetRemote "https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js" }}
{{ $track := resources.Get "js/track.js" | resources.ExecuteAsTemplate "js/track.js" . }}
{{- $analytics := slice $track $script | resources.Concat "js/analytics.js" | minify | fingerprint }}
<script src="{{ $analytics.RelPermalink }}" integrity="{{ $analytics.Data.Integrity }}" async></script>
{{- end }}
  1. Include the tracking code in the configuration file as in method 1.

  2. Add the partial in your template as in method 1.

Method 3

  1. track.js
Summary
import params from "@params";
window.minimalAnalytics = {
trackingId: params.trackingId,
autoTrack: true, // <-- init tracking
};
  1. analytics.html
Summary
{{- with site.Params.google.analytics.id }}
{{- $js := resources.GetRemote "https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js" }}
{{- $track := resources.Get "track.js" -}}
{{- $opts := dict
"params" (dict "trackingId" site.Params.google.analytics.id) 
-}}
{{- $track = $js | js.Build $opts -}}
{{- $analytics := slice $track $script | resources.Concat "js/analytics.js" | minify | fingerprint }}
<script src="{{ $analytics.RelPermalink }}" integrity="{{ $analytics.Data.Integrity }}" async></script>
{{- end }}
  1. Include the tracking code in the configuration file as in method 1.

  2. Add the partial in your template as in method 1.

(Credit)

Side note

If you want this code to only work in production, wrap the <script> part in hugo.IsProduction, e.g

Summary
{{ if hugo.IsProduction }}
<script src="{{ $analytics.RelPermalink }}" integrity="{{ $analytics.Data.Integrity }}" async></script>
{{ end }}
2 Likes

You can also just replace the default analytics file by creating /layouts/_internal/google_analytics.html.

  • This way you can just use the default GA param of Hugo googleAnalytics = ""
  • If the theme is using Hugo’s default GA call, you don’t need to add a call to a custom partial. Makes switching themes much easier.

I was avoiding having to interrupt internal template functions. But anyone can use your feature if they prefer it.

I have created mine as a universal approach, so can be used by passing in <head> of any site. If we want to integrate it with Hugo’s config file, that’s not too difficult.

Just in minified script replace G-XXXXXXXXXX with reference to config file with {{ .Site.Params.google.analytics }} and then put this in a config file

[params.google]
analytics = "G-XXXXXXXXXX"

5 posts were split to a new topic: Error: can’t evaluate field ConCat in type interface

Hi,
Worth to add to the first post that James solution is using TypeScrypt and mine is using JavaScript Vanilla. Both great, and I see that James implements a lot of good things in his which I am trying to implement in mine as well along with my learning curve.

ps. Just updated mine to version 1.09 with scroll tracking if you want to update as well.

Scrolls
Capture scroll events each time that a visitor gets to the bottom of a page.

1 Like

The CDN section uses vanilla JS, which I used in this tutorial. If you can, refactor your code to split the tracking part from the main code as he has, I will redo the tutorial to include your code.

I implemented this with pleasure only to discover the same night that Austria and France have banned GA already.

I don’t think there is a work around for that so I guess we have to do without it?

1 Like

If it is due to GDPR matters, then that’s beyond the scope of this script. You can google how to make GA4 GDPR friendly.

Hello I’m new! when I get the code on my site I get the error: Error: add site dependencies: load resources: loading templates: "/home/alova/Projects/Websites/mysite/layouts/partials/analytics.html:1:1": parse failed: template: partials/analytics.html:1: function "withsite" not defined

Also how does one add google analytics to connect-src? And what are those?

Try copy-pasting the code now, depending on the method you’ve chosen. Somehow, there were errors with the code that I have corrected.

See CSP: connect-src - HTTP | MDN

Thank you! I think it works now, google analytics still gives me the “Data collection isn’t active for your website.” warning but I just saw 2 users visited the site.
Is there a way to test it?

Check the live stats section of GA4 in your account. It might take a while for results to show. Personally, I use method 3.

I can’t recommend zaraz within CloudFlare enough. It’s super easy to setup and sped up our websites by not having the page load a js library.

Live stats? Do you mean Reports/Realtime? I did see some activity there a few hours ago but right now nothing happens when I connect to the site myself.

Yes. If you have implemented everything properly, there should be stats appearing there for the past 30 minutes if you have visitors on your site. Also, ensure you don’t have an ad blocker when viewing the site yourself.

Not sure if It is politically correct to say so, but may be we should warn people that using GA is illegal in some European countries. Really.

That’s beyond the scope of this post.

Well … i understand your point, but providing tools in stock hugo that are illegal in certain countries (and not only problematic for RGPD, … really Illegal), may be needs a warning.
Not everyone is aware of this.

1 Like

Ah yeah sorry, I forgot about adblockers. Now it works fine. Thank you!