From 2671d8f226d671ae2e6976bea63e74f9231b3292 Mon Sep 17 00:00:00 2001 From: Sam Kim Date: Wed, 23 Sep 2026 23:25:17 -0700 Subject: [PATCH] feat(consent): add Google Tag Manager with Consent Mode v2 Mirrors the GTM + Consent Mode v2 setup from authzed/web, adapted for docs' read-only consumer role: since docs shares the `az-consent` cookie set by the marketing site and never shows its own banner, the consent default is resolved server-side from the cookie (no flash, no wait_for_update) and falls back to web's region-based default when no cookie is present yet. Shared logic lives in lib/consent/gtm.ts. Co-Authored-By: Claude Sonnet 5 --- app/layout.tsx | 37 ++++++++++++++- lib/consent/gtm.ts | 106 +++++++++++++++++++++++++++++++++++++++++++ lib/consent/index.ts | 1 + 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 lib/consent/gtm.ts diff --git a/app/layout.tsx b/app/layout.tsx index 5400cf67..4b68637c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -12,6 +12,15 @@ import { ContentStatus } from "@/components/content-status"; import Scripts from "@/components/scripts"; import type { Metadata, ResolvingMetadata } from "next"; import { SpeedInsights } from "@vercel/speed-insights/next"; +import { cookies } from "next/headers"; +import NextScript from "next/script"; +import { + CONSENT_COOKIE_NAME, + parseConsentCookie, + GTM_ID, + buildConsentDefaultScript, + buildGtmLoaderScript, +} from "@/lib/consent"; import "./globals.css"; import { default as OurLayout } from "@/components/layout"; @@ -64,6 +73,9 @@ export default async function RootLayout({ children }) { const pageMap = await getPageMap(); const enableSearch = process.env.NEXT_PUBLIC_ENABLE_SEARCH_BLOG_INTEGRATION === "true"; const starCount = await getStarCount(); + const cookieStore = await cookies(); + const consent = parseConsentCookie(cookieStore.get(CONSENT_COOKIE_NAME)?.value); + const consentDefaultScript = buildConsentDefaultScript(consent); const navbar = ( + > + {/* Google Consent Mode v2 defaults — must run before GTM */} +