From e9fce7d458d7490d37e541b07f1879e826d13584 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 15 Sep 2026 20:39:16 +0800 Subject: [PATCH] web: load Google Analytics only after the visitor accepts it Analytics.astro rendered whenever config.yaml carried a measurement id, so gtag ran and the _ga cookies were set on the first page view, before the banner was answered and whatever the visitor went on to choose. The analytics service in the consent config was never wired up to anything -- its onAccept was a `// TODO: load ga4` -- so consent decided nothing either way. The privacy policy says the opposite, that cookies are used on the basis of consent given through the banner. Analytics.astro now only defines window.loadGoogleAnalytics, and the ga4 service calls it from onAccept, so Google is first contacted after the visitor accepts. Rejecting leaves the loader uncalled, and the library erases the _ga cookies it already matches. CookieConsent.astro skipped run() once its own localStorage flag was set, which meant the library was not initialised on any later visit -- with the loader moved behind consent that would have left analytics permanently dead instead of permanently on, since re-applying a stored consent is what calls onAccept. It now runs on every page load and lets the library decide whether to show the banner. Consent could also not be withdrawn: the preferences modal had no trigger anywhere on the site, and the privacy policy offered browser settings instead, which is not a withdrawal mechanism and is not as easy as giving consent was. The footer now links to the modal and the policy points at it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab --- v3/src/components/CookieConsent.astro | 26 +++++++++++------- v3/src/components/CookieConsentConfig.ts | 16 ++++++++--- v3/src/components/common/Analytics.astro | 34 +++++++++++++++++------- v3/src/navigation.ts | 16 +++++++++++ v3/src/pages/privacy.md | 2 +- 5 files changed, 70 insertions(+), 24 deletions(-) diff --git a/v3/src/components/CookieConsent.astro b/v3/src/components/CookieConsent.astro index 912e8920..7e1b145e 100644 --- a/v3/src/components/CookieConsent.astro +++ b/v3/src/components/CookieConsent.astro @@ -3,21 +3,27 @@ import 'vanilla-cookieconsent/dist/cookieconsent.css'; import '../assets/styles/ccElegantBlack.css'; --- - diff --git a/v3/src/components/CookieConsentConfig.ts b/v3/src/components/CookieConsentConfig.ts index 41fe8335..a73c21bb 100644 --- a/v3/src/components/CookieConsentConfig.ts +++ b/v3/src/components/CookieConsentConfig.ts @@ -11,6 +11,12 @@ import ko from './cookie/ko.json'; import zhCN from './cookie/zh-CN.json'; import zhTW from './cookie/zh-TW.json'; +declare global { + interface Window { + loadGoogleAnalytics?: () => void; + } +} + export const config: CookieConsentConfig = { guiOptions: { consentModal: { @@ -37,12 +43,14 @@ export const config: CookieConsentConfig = { ga4: { label: 'Google Analytics 4', + // Defined by Analytics.astro, which only declares the loader and never calls + // it, so Google is contacted for the first time here -- after consent. onAccept: () => { - // TODO: load ga4 - }, - onReject: () => { - console.log('ga4 rejected'); + window.loadGoogleAnalytics?.(); }, + // Nothing to undo: rejecting means the loader was never called, and the + // cookies matched below are erased by the library itself. + onReject: () => {}, cookies: [ { name: /^_ga/, diff --git a/v3/src/components/common/Analytics.astro b/v3/src/components/common/Analytics.astro index a1a553dd..c4d8ba02 100644 --- a/v3/src/components/common/Analytics.astro +++ b/v3/src/components/common/Analytics.astro @@ -1,13 +1,29 @@ --- -import { GoogleAnalytics } from '@astrolib/analytics'; import { ANALYTICS } from 'astrowind:config'; + +const gaId = ANALYTICS?.vendors?.googleAnalytics?.id ? String(ANALYTICS.vendors.googleAnalytics.id) : ''; + +// Nothing is loaded here. This only defines the loader; the analytics service in +// CookieConsentConfig calls it once the visitor accepts that category, so no Google +// script runs and no _ga cookie is set before consent is given. +const loader = ` +window.loadGoogleAnalytics = function () { + if (window.__gaLoaded) return; + window.__gaLoaded = true; + + var s = document.createElement('script'); + s.async = true; + s.src = 'https://www.googletagmanager.com/gtag/js?id=${gaId}'; + document.head.appendChild(s); + + window.dataLayer = window.dataLayer || []; + window.gtag = function () { + window.dataLayer.push(arguments); + }; + window.gtag('js', new Date()); + window.gtag('config', '${gaId}'); +}; +`; --- -{ - ANALYTICS?.vendors?.googleAnalytics?.id ? ( - - ) : null -} +{gaId ?