{{/*
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. A site setting params.contentSecurityPolicy has to allow
googletagmanager.com in TWO directives, which is easy to get half right:
script-src https://www.googletagmanager.com gtag.js itself
img-src https://www.googletagmanager.com the /td tag-diagnostics ping
img-src https://*.google-analytics.com the /g/collect image fallback
The /td ping fires only on some page loads, so a clean home page proves nothing about
img-src. Blocking it costs no measurement, but it logs a CSP error that then hides real
problems in the console. connect-src needs nothing as long as the policy sets no
default-src, since an unset directive with nothing to fall back to is unrestricted.
Build warnings. Each is a suppressible ID, so silencing one means writing
ignoreLogs = [''] into the site config, which records the decision instead of leaving
the obligation unnoticed:
google-tag-cookies-allowed cookies = true, so the site owes its visitors a banner
google-tag-no-measurement-id [googleTag] present but measurementId empty
google-tag-csp-blocked a CSP is set and script-src omits googletagmanager.com
That last check deliberately covers script-src only. The policy is one opaque string, so a
substring test cannot tell which directive the host appears in, and it would pass on
script-src alone while img-src silently blocks the /td ping.
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 }}