{{/*
Google tag (gtag.js) with Consent Mode defaults.
Site params:
[googleTag]
measurementId = "G-XXXXXXXXXX" # GA4 measurement ID
googleAds = "AW-XXXXXXXXX" # optional, a second config target
cookies = false # true lets the tag set cookies
With `cookies` unset or false, google-tag-consent.js defaults every consent type to
'denied', so the tag sends cookieless pings and writes nothing to the device. Never
sending a 'consent' 'update' keeps it that way, which is a supported configuration for a
site that wants audience measurement without a consent banner. Reporting is limited in
that mode: event counts by country, device and channel, with no users, sessions or
attribution.
All three scripts are `defer`, and that is a correctness requirement rather than a
performance preference. Google requires the 'consent' 'default' command to run before the
tag library initialises. `async` scripts execute in completion order, so an async consent
script races gtag.js from Google's CDN, which is frequently already cached, and loses
often enough to set the very cookies it exists to prevent. `defer` executes in document
order across origins, which guarantees the sequence without blocking the parser. The cost
is that measurement begins once parsing finishes rather than as early as possible.
head/head.html calls this partial after , not before it. The HTML spec wants
the charset declaration within the first 1024 bytes, and the two fingerprinted script tags
below carry SHA512 integrity attributes of roughly 88 characters each, so emitting them
first spends a large part of that budget for nothing. It is still early enough for Google's
"as high as possible in the head" guidance.
Content-Security-Policy: this loads a script from www.googletagmanager.com and the tag
beacons to *.google-analytics.com. A site setting params.contentSecurityPolicy must allow
both or the browser blocks the tag with nothing but a console error, so the check below
turns that into a build warning.
See:
- https://developers.google.com/tag-platform/devguides/consent
- https://developers.google.com/tag-platform/security/guides/csp
- https://developers.google.com/tag-platform/tag-manager/web/csp
*/}}
{{- with site.Params.googleTag }}
{{- $measurementId := .measurementId | default "" }}
{{- $googleAds := .googleAds | default "" }}
{{- /* Deliberately `eq ... true` rather than a truthiness test: a mistyped value such as
the string "true" then falls through to the cookieless path, which is the safe
direction to fail in. */ -}}
{{- $allowCookies := eq .cookies true }}
{{- /* Outside the measurementId guard on purpose. A site that opts into cookies has made a
consent decision worth flagging even while its measurement ID is still commented
out, because the ID is the part most likely to be filled in later without anyone
revisiting the consent question. */ -}}
{{- if $allowCookies }}
{{- warnidf "google-tag-cookies-allowed" "head/google-tag.html: googleTag.cookies is true, so the Google tag sets cookies without asking. Most jurisdictions require consent for non-essential cookies (EU/UK: GDPR, ePrivacy, PECR; Brazil: LGPD), which means this site needs a banner calling gtag('consent', 'update', ...). Set cookies = false for cookieless measurement that needs no banner." }}
{{- end }}
{{- if not $measurementId }}
{{- warnidf "google-tag-no-measurement-id" "head/google-tag.html: params.googleTag is set but measurementId is empty, so no tag was emitted. Remove the [googleTag] block if that is intentional." }}
{{- else }}
{{- with site.Params.contentSecurityPolicy }}
{{- if not (strings.Contains . "googletagmanager.com") }}
{{- warnidf "google-tag-csp-blocked" "head/google-tag.html: params.contentSecurityPolicy omits googletagmanager.com, so the browser will block gtag.js and only a console error will show it. Add https://www.googletagmanager.com to script-src, and https://*.google-analytics.com to img-src for the image-beacon fallback." }}
{{- end }}
{{- end }}
{{- /* RelPermalink, not Permalink: an absolute URL built from baseURL points a deploy
preview at the production host, which with integrity set makes it a cross-origin
request that fails CORS and silently drops the tag. Same-origin subresource
integrity needs no crossorigin attribute. */ -}}
{{- if not $allowCookies }}
{{- with resources.Get "js/google-tag-consent.js" }}
{{- $consent := . }}
{{- if hugo.IsProduction }}{{ $consent = $consent | minify }}{{ end }}
{{- $consent = $consent | fingerprint "sha512" }}
{{- else }}
{{- errorf "head/google-tag.html: assets/js/google-tag-consent.js not found, so Consent Mode defaults cannot be emitted. Set googleTag.cookies = true to opt out of Consent Mode instead." }}
{{- end }}
{{- end }}
{{- with resources.Get "js/google-tag.js" }}
{{- $opts := dict
"params" (dict "measurementId" $measurementId "googleAds" $googleAds)
"minify" hugo.IsProduction
}}
{{- $tag := . | js.Build $opts | fingerprint "sha512" }}
{{- else }}
{{- errorf "head/google-tag.html: assets/js/google-tag.js not found." }}
{{- end }}
{{- end }}
{{- end }}