{{/* 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 }}