From b179ea34a43cac5aa485469f90621c54003fb941 Mon Sep 17 00:00:00 2001 From: Stamen Stoychev Date: Thu, 13 Aug 2026 16:44:29 +0300 Subject: [PATCH] fix(*): adding google tag manager artifacts for tracking --- src/integration.ts | 13 ++++++++++++- src/lib/platform-context.ts | 25 +++++++++++++++++++++++++ src/middleware.ts | 26 +++++++++++++++++++++++--- 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/integration.ts b/src/integration.ts index dc3bbbf0ad..4feb91f694 100644 --- a/src/integration.ts +++ b/src/integration.ts @@ -60,6 +60,7 @@ import { import { buildSidebarFromToc } from './sidebar'; import { getPlatformHead } from './platform'; import type { HeadEntry, PlatformKey, NavLang } from './platform.ts'; +import { getGtmContainerId } from './lib/platform-context.js'; import { remarkEnvVars } from './plugins/remark-env-vars'; import { remarkMdLinks } from './plugins/remark-md-links'; import { remarkHtmlTransforms } from './plugins/remark-html-transforms'; @@ -667,6 +668,16 @@ export function createDocsSite(options: CreateDocsSiteOptions = {} as CreateDocs process.env.DOCS_PLATFORM = platform; } + // Google Tag Manager — first in , as high as possible after the + // mandatory charset/viewport/title tags rendered by DocsLayout. Container ID + // is resolved per build mode (production vs. staging/development); see + // `getGtmContainerId()` for the resolution order. + const gtmContainerId = getGtmContainerId(); + const gtmHead: HeadEntry[] = [{ + tag: 'script', + content: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${gtmContainerId}');`, + }]; + // Platform CDN entries come first so site-specific `head` entries can override. const platformHead = platform ? getPlatformHead(platform, navLang) : []; @@ -755,7 +766,7 @@ export function createDocsSite(options: CreateDocsSiteOptions = {} as CreateDocs productLinks, packages, selectedPackage, - head: [...platformHead, ...codeViewHead, ...head], + head: [...gtmHead, ...platformHead, ...codeViewHead, ...head], }), ...(base ? [createBasePrependIntegration(base)] : []), ...extraIntegrations, diff --git a/src/lib/platform-context.ts b/src/lib/platform-context.ts index 454023e092..6525a2e8e1 100644 --- a/src/lib/platform-context.ts +++ b/src/lib/platform-context.ts @@ -262,3 +262,28 @@ export function getEnvVars(): Record { return _env!; } + +/** Built-in Google Tag Manager container IDs, keyed by build mode. */ +const GTM_CONTAINER_ID_DEFAULTS: Record = { + production: 'GTM-T65CF7', + staging: 'GTM-NCKNPN', + development: 'GTM-NCKNPN', +}; + +/** + * Resolves the Google Tag Manager container ID for the current build. + * + * Resolution order: + * 1. `GTM_CONTAINER_ID` env var — explicit override. + * 2. `GTMContainerId` in the resolved `environment.json` (per-mode, per-locale). + * 3. Built-in default for the current build mode — production vs. staging/development. + */ +export function getGtmContainerId(): string { + if (process.env.GTM_CONTAINER_ID) return process.env.GTM_CONTAINER_ID; + + const fromEnvJson = getEnvVars().GTMContainerId; + if (fromEnvJson) return fromEnvJson; + + const mode = getBuildMode(); + return GTM_CONTAINER_ID_DEFAULTS[mode] ?? GTM_CONTAINER_ID_DEFAULTS.development; +} diff --git a/src/middleware.ts b/src/middleware.ts index 7e5b123fef..7dcf116d4a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,8 +1,28 @@ import { defineMiddleware } from 'astro:middleware'; -import { getPlatformContext, getEnvVars } from './lib/platform-context.js'; +import { getPlatformContext, getEnvVars, getGtmContainerId } from './lib/platform-context.js'; -export const onRequest = defineMiddleware((ctx, next) => { +export const onRequest = defineMiddleware(async (ctx, next) => { ctx.locals.platformContext = getPlatformContext(); ctx.locals.envVars = getEnvVars(); - return next(); + + const response = await next(); + + // Only rewrite HTML documents — skip .md/.txt/.xml endpoints and static assets. + const contentType = response.headers.get('content-type') ?? ''; + if (!contentType.includes('text/html')) return response; + + const html = await response.text(); + if (!/]*>/i.test(html)) return response; + + const gtmNoscript = ``; + const updated = html.replace(/]*)>/i, `${gtmNoscript}`); + + const headers = new Headers(response.headers); + headers.delete('content-length'); + + return new Response(updated, { + status: response.status, + statusText: response.statusText, + headers, + }); });