From 304b253d15e105b7be3c4c1d475e642e29d41625 Mon Sep 17 00:00:00 2001 From: Steven Serrata Date: Tue, 21 Jul 2026 10:25:20 -0500 Subject: [PATCH 1/3] feat(analytics): gate GA4/GTM behind Google Consent Mode v2 Fold cookie-consent behavior into the custom docusaurus-plugin-gtm rather than adopting a third-party plugin, so the consent signaling and the GTM loader are emitted from a single injectHtmlTags call. This guarantees the `consent default = denied` script runs before GTM loads by array order, independent of plugin registration order. - docusaurus-plugin-gtm: inject Consent Mode v2 defaults (all denied except security_storage), replay stored choice from localStorage, and set ads_data_redaction, all before the GTM loader. Prod-only, unchanged otherwise. Adds storageKey/waitForUpdate options. - src/components/CookieConsent: SSR-safe bottom banner (Accept all / Reject optional) plus a shared consent helper that writes localStorage and fires gtag('consent','update') with the category -> Google signal mapping. - src/theme/Root.js: render the banner globally. Verified against a production build: compiled HTML emits the consent default script before the GTM loader. Co-Authored-By: Claude Opus 4.8 --- docusaurus-plugin-gtm/index.js | 37 +++++++++++++ src/components/CookieConsent/consent.js | 58 ++++++++++++++++++++ src/components/CookieConsent/index.js | 54 +++++++++++++++++++ src/components/CookieConsent/styles.scss | 69 ++++++++++++++++++++++++ src/theme/Root.js | 9 +++- 5 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/components/CookieConsent/consent.js create mode 100644 src/components/CookieConsent/index.js create mode 100644 src/components/CookieConsent/styles.scss diff --git a/docusaurus-plugin-gtm/index.js b/docusaurus-plugin-gtm/index.js index afb8de86f..0bd1b0c3b 100644 --- a/docusaurus-plugin-gtm/index.js +++ b/docusaurus-plugin-gtm/index.js @@ -1,13 +1,50 @@ module.exports = function (context, options) { const isProd = process.env.NODE_ENV === "production"; + const storageKey = options.storageKey || "cookie-consent-preferences"; + const waitForUpdate = options.waitForUpdate || 500; + return { name: "docusaurus-plugin-gtm", injectHtmlTags() { if (!isProd) { return {}; } + + const consentDefault = ` +(function(){ + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + window.gtag = window.gtag || gtag; + gtag('consent','default',{ + ad_storage:'denied', + ad_user_data:'denied', + ad_personalization:'denied', + analytics_storage:'denied', + functionality_storage:'denied', + personalization_storage:'denied', + security_storage:'granted', + wait_for_update:${waitForUpdate} + }); + gtag('set','ads_data_redaction',true); + try { + var c = JSON.parse(localStorage.getItem(${JSON.stringify(storageKey)})); + if (c && c.consentGiven) { + gtag('consent','update',{ + ad_storage: c.marketing?'granted':'denied', + ad_user_data: c.marketing?'granted':'denied', + ad_personalization: c.marketing?'granted':'denied', + analytics_storage: c.analytics?'granted':'denied', + functionality_storage: c.functional?'granted':'denied', + personalization_storage: c.functional?'granted':'denied' + }); + } + } catch (e) {} +})();`; + return { headTags: [ + // Consent Mode defaults must run before the GTM loader below. + { tagName: "script", innerHTML: consentDefault }, `