Skip to content

Commit 807ea2d

Browse files
committed
feat: implement Google Consent Mode v2 with cookieless pings for Google Ads
1 parent d0e519d commit 807ea2d

File tree

3 files changed

+144
-55
lines changed

3 files changed

+144
-55
lines changed
Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,110 @@
11
"use client"
22

3-
import { useEffect, useState } from "react"
3+
import { useEffect } from "react"
44
import Script from "next/script"
55
import { hasConsent, onConsentChange } from "@/lib/analytics/consent-manager"
66

7-
// Google Tag Manager ID
7+
// Google Ads Conversion ID
88
const GTM_ID = "AW-17391954825"
99

1010
/**
11-
* Google Analytics Provider
12-
* Only loads Google Tag Manager after user gives consent
11+
* Google Analytics Provider with Consent Mode v2
12+
* Implements advanced consent mode with cookieless pings
1313
*/
1414
export function GoogleAnalyticsProvider({ children }: { children: React.ReactNode }) {
15-
const [shouldLoad, setShouldLoad] = useState(false)
16-
1715
useEffect(() => {
18-
// Check initial consent status
19-
if (hasConsent()) {
20-
setShouldLoad(true)
21-
initializeGoogleAnalytics()
16+
// Initialize dataLayer and gtag immediately for consent mode
17+
if (typeof window !== "undefined") {
18+
window.dataLayer = window.dataLayer || []
19+
window.gtag = function (...args: unknown[]) {
20+
window.dataLayer.push(args)
21+
}
22+
23+
// Set default consent state for Consent Mode v2
24+
// This must be called before any Google tags load
25+
window.gtag("consent", "default", {
26+
ad_storage: "denied",
27+
ad_user_data: "denied",
28+
ad_personalization: "denied",
29+
analytics_storage: "denied",
30+
functionality_storage: "denied",
31+
personalization_storage: "denied",
32+
security_storage: "granted", // Always granted for security
33+
wait_for_update: 2000, // Wait up to 2 seconds for consent update
34+
// Enable cookieless pings for conversion measurement
35+
// This allows Google Ads to measure conversions without cookies
36+
url_passthrough: true, // Pass click information via URL parameters
37+
ads_data_redaction: true, // Redact ads data when consent is denied
38+
})
39+
40+
// Check initial consent status and update if already consented
41+
if (hasConsent()) {
42+
updateConsentState(true)
43+
}
2244
}
2345

2446
// Listen for consent changes
2547
const unsubscribe = onConsentChange((consented) => {
26-
if (consented && !shouldLoad) {
27-
setShouldLoad(true)
28-
initializeGoogleAnalytics()
29-
}
48+
updateConsentState(consented)
3049
})
3150

3251
return unsubscribe
33-
}, [shouldLoad])
52+
}, [])
3453

35-
const initializeGoogleAnalytics = () => {
36-
// Initialize the dataLayer and gtag function
37-
if (typeof window !== "undefined") {
38-
window.dataLayer = window.dataLayer || []
39-
window.gtag = function (...args: GtagArgs) {
40-
window.dataLayer.push(args)
41-
}
42-
window.gtag("js", new Date())
43-
window.gtag("config", GTM_ID)
44-
}
45-
}
54+
const updateConsentState = (consented: boolean) => {
55+
if (typeof window !== "undefined" && window.gtag) {
56+
// Update consent state based on user choice
57+
window.gtag("consent", "update", {
58+
ad_storage: consented ? "granted" : "denied",
59+
ad_user_data: consented ? "granted" : "denied",
60+
ad_personalization: consented ? "granted" : "denied",
61+
analytics_storage: consented ? "granted" : "denied",
62+
functionality_storage: consented ? "granted" : "denied",
63+
personalization_storage: consented ? "granted" : "denied",
64+
})
4665

47-
// Only render Google Analytics scripts if consent is given
48-
if (!shouldLoad) {
49-
return <>{children}</>
66+
console.log(`Google Consent Mode updated: ${consented ? "granted" : "denied"}`)
67+
}
5068
}
5169

5270
return (
5371
<>
54-
{/* Google tag (gtag.js) - Only loads after consent */}
72+
{/* Google tag (gtag.js) - Loads immediately for Consent Mode v2 */}
5573
<Script
5674
src={`https://www.googletagmanager.com/gtag/js?id=${GTM_ID}`}
5775
strategy="afterInteractive"
5876
onLoad={() => {
59-
console.log("Google Analytics loaded with consent")
77+
console.log("Google Analytics loaded with Consent Mode v2")
6078
}}
6179
/>
62-
<Script id="google-analytics-init" strategy="afterInteractive">
80+
<Script id="google-analytics-consent-mode" strategy="afterInteractive">
6381
{`
6482
window.dataLayer = window.dataLayer || [];
6583
function gtag(){dataLayer.push(arguments);}
6684
gtag('js', new Date());
67-
gtag('config', '${GTM_ID}');
85+
86+
// Configure with enhanced measurement and cookieless pings
87+
gtag('config', '${GTM_ID}', {
88+
allow_google_signals: false, // Disable by default, enabled when consent granted
89+
allow_ad_personalization_signals: false, // Disable by default
90+
// Enable enhanced conversions for better measurement
91+
enhanced_conversions: {
92+
automatic: true
93+
},
94+
// Send page view for cookieless measurement
95+
send_page_view: true
96+
});
6897
`}
6998
</Script>
7099
{children}
71100
</>
72101
)
73102
}
74103

75-
// Type definitions for Google Analytics
76-
type GtagArgs = ["js", Date] | ["config", string, GtagConfig?] | ["event", string, GtagEventParameters?]
77-
78-
interface GtagConfig {
79-
[key: string]: unknown
80-
}
81-
82-
interface GtagEventParameters {
83-
[key: string]: unknown
84-
}
85-
86104
// Declare global types for TypeScript
87105
declare global {
88106
interface Window {
89-
dataLayer: GtagArgs[]
90-
gtag: (...args: GtagArgs) => void
107+
dataLayer: unknown[]
108+
gtag: (...args: unknown[]) => void
91109
}
92110
}

apps/web-roo-code/src/lib/analytics/consent-manager.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,54 @@ export function onConsentChange(callback: (consented: boolean) => void): () => v
4949

5050
/**
5151
* Handle user accepting cookies
52-
* Opts PostHog back into cookie-based tracking
52+
* Opts PostHog back into cookie-based tracking and updates Google Consent Mode
5353
*/
5454
export function handleConsentAccept(): void {
55-
if (typeof window !== "undefined" && posthog.__loaded) {
56-
// User accepted - ensure localStorage+cookie persistence is enabled
57-
posthog.opt_in_capturing()
58-
posthog.set_config({
59-
persistence: "localStorage+cookie",
60-
})
55+
if (typeof window !== "undefined") {
56+
// Update PostHog consent
57+
if (posthog.__loaded) {
58+
// User accepted - ensure localStorage+cookie persistence is enabled
59+
posthog.opt_in_capturing()
60+
posthog.set_config({
61+
persistence: "localStorage+cookie",
62+
})
63+
}
64+
65+
// Update Google Consent Mode v2 immediately
66+
if ("gtag" in window && typeof window.gtag === "function") {
67+
;(window as Window & { gtag: (...args: unknown[]) => void }).gtag("consent", "update", {
68+
ad_storage: "granted",
69+
ad_user_data: "granted",
70+
ad_personalization: "granted",
71+
analytics_storage: "granted",
72+
functionality_storage: "granted",
73+
personalization_storage: "granted",
74+
})
75+
console.log("User accepted cookies - Google Consent Mode updated to granted")
76+
}
6177
}
6278
dispatchConsentEvent(true)
6379
}
6480

6581
/**
6682
* Handle user rejecting cookies
67-
* Switches PostHog to cookieless (memory-only) mode
83+
* Switches PostHog to cookieless (memory-only) mode and updates Google Consent Mode
6884
*/
6985
export function handleConsentReject(): void {
86+
if (typeof window !== "undefined") {
87+
// Update Google Consent Mode v2 immediately
88+
if ("gtag" in window && typeof window.gtag === "function") {
89+
;(window as Window & { gtag: (...args: unknown[]) => void }).gtag("consent", "update", {
90+
ad_storage: "denied",
91+
ad_user_data: "denied",
92+
ad_personalization: "denied",
93+
analytics_storage: "denied",
94+
functionality_storage: "denied",
95+
personalization_storage: "denied",
96+
})
97+
console.log("User rejected cookies - Google Consent Mode updated to denied")
98+
}
99+
}
70100
// User rejected - stick to cookieless mode
71101
dispatchConsentEvent(false)
72102
}
Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,58 @@
11
/**
22
* Google Ads conversion tracking utilities
3+
* Works with Google Consent Mode v2 for cookieless measurement
34
*/
45

56
/**
67
* Track a Google Ads conversion event
7-
* This should only be called after user consent has been given
8+
* With Consent Mode v2, this will:
9+
* - Send full conversion data when consent is granted
10+
* - Send cookieless conversion pings when consent is denied
11+
* - Use enhanced conversions for better measurement accuracy
812
*/
913
export function trackGoogleAdsConversion() {
1014
if (typeof window !== "undefined" && window.gtag) {
15+
// Conversion will be tracked according to current consent state
16+
// If consent is denied, Google will use cookieless pings
17+
// If consent is granted, full conversion tracking will be used
1118
window.gtag("event", "conversion", {
1219
send_to: "AW-17391954825/VtOZCJe_77MbEInXkOVA",
1320
value: 10.0,
1421
currency: "USD",
22+
// Additional parameters for enhanced conversion tracking
23+
transaction_id: `${Date.now()}`, // Unique ID to prevent duplicates
24+
})
25+
26+
console.log("Google Ads conversion tracked with Consent Mode v2")
27+
}
28+
}
29+
30+
/**
31+
* Track a page view for remarketing
32+
* Works with Consent Mode v2 for cookieless measurement
33+
*/
34+
export function trackPageViewForRemarketing() {
35+
if (typeof window !== "undefined" && window.gtag) {
36+
// Page view will be tracked according to current consent state
37+
window.gtag("event", "page_view", {
38+
send_to: "AW-17391954825",
39+
// These parameters help with cookieless measurement
40+
// They will be redacted if consent is denied
41+
})
42+
}
43+
}
44+
45+
/**
46+
* Initialize enhanced conversions for better measurement
47+
* This should be called once when the app initializes
48+
*/
49+
export function initializeEnhancedConversions() {
50+
if (typeof window !== "undefined" && window.gtag) {
51+
// Enable enhanced conversions for better measurement accuracy
52+
// This works with Consent Mode v2 to provide better conversion modeling
53+
window.gtag("set", "user_data", {
54+
// User data will only be sent if consent is granted
55+
// Otherwise, Google uses conversion modeling
1556
})
1657
}
1758
}

0 commit comments

Comments
 (0)