feat(docs): wire Google Analytics into the published docs site - #1482
Conversation
…ppet Pass the GA Measurement ID from the GitHub Actions repo variable into the docs build so the published site at mempalaceofficial.com actually emits the gtag tags. Also escape the ID via encodeURIComponent / JSON.stringify so a malformed value can't break the page.
There was a problem hiding this comment.
Code Review
This pull request updates the VitePress configuration to properly encode and stringify the Google Analytics ID in the generated script tags. Review feedback points out that the necessary environment variable mapping in the GitHub Actions workflow is missing, which would prevent the ID from being available during the build process. Additionally, a potential XSS vulnerability was identified because JSON.stringify does not escape the HTML closing script tag, and a suggestion was provided to escape the character to harden the implementation.
| ['script', { async: '', src: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaId)}` }], | ||
| ['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', ${JSON.stringify(gaId)});`], |
There was a problem hiding this comment.
The pull request description mentions passing MEMPALACE_DOCS_GA_ID from the deploy workflow, but the necessary changes to the GitHub Actions workflow files (typically located in .github/workflows/) are not included in this PR. \n\nIn GitHub Actions, repository variables are not automatically injected into the environment of run steps. To ensure the analytics ID is available during the VitePress build, you must explicitly map it in your workflow file:\n\nyaml\n- name: Build Docs\n run: npm run docs:build\n env:\n MEMPALACE_DOCS_GA_ID: ${{ vars.MEMPALACE_DOCS_GA_ID }}\n\n\nWithout this, gaId will remain undefined in the production build, and the Google Analytics tags will not be rendered.
| ['script', { async: '', src: `https://www.googletagmanager.com/gtag/js?id=${gaId}` }], | ||
| ['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', '${gaId}');`], | ||
| ['script', { async: '', src: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaId)}` }], | ||
| ['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', ${JSON.stringify(gaId)});`], |
There was a problem hiding this comment.
While using JSON.stringify is a good improvement for handling quotes and special characters, it does not escape the HTML closing script tag </script>. If the environment variable were to contain this sequence, it could prematurely terminate the script block and lead to a Cross-Site Scripting (XSS) vulnerability. To fully harden the snippet as intended in the PR description, consider escaping the < character.
['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', ${JSON.stringify(gaId).replace(/</g, '\\u003c')});`],
There was a problem hiding this comment.
Pull request overview
Wires the Google Analytics measurement ID from the GitHub Pages deploy workflow into the VitePress build so the published docs site can conditionally emit GA/gtag tags, and hardens the generated script tags against malformed IDs.
Changes:
- Pass
MEMPALACE_DOCS_GA_IDfrom GitHub Actions repo Variables into the docs build step. - Update the VitePress
headGA snippet to URL-encode the ID in the loader URL and to safely embed the ID in the inlinegtag('config', ...)call.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
website/.vitepress/config.mts |
Safens GA script URL composition and inline config emission when MEMPALACE_DOCS_GA_ID is present. |
.github/workflows/deploy-docs.yml |
Injects MEMPALACE_DOCS_GA_ID into the docs build environment so GA tags can be emitted at build time. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ...(gaId ? [ | ||
| ['script', { async: '', src: `https://www.googletagmanager.com/gtag/js?id=${gaId}` }], | ||
| ['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', '${gaId}');`], | ||
| ['script', { async: '', src: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaId)}` }], | ||
| ['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', ${JSON.stringify(gaId)});`], |
Summary
MEMPALACE_DOCS_GA_ID(set as a repo Variable) from the deploy workflow into the VitePress build, so the published site at mempalaceofficial.com actually emits the gtag tags. Today the conditional inconfig.mtsexists but the env var was never injected, so GA shipped as dead code.encodeURIComponenton the script URL andJSON.stringifyfor the inlinegtag('config', ...)call, so a malformed value can't break the page.The privacy/local-first guarantees in CLAUDE.md apply to the Python product, not the marketing/docs site, so analytics on the public site is in scope.
Test plan
MEMPALACE_DOCS_GA_IDrepo Variable is set in Settings → Secrets and variables → Actions → Variables.developand let theDeploy Docsworkflow run.<script>tags pointing atgoogletagmanager.com/gtag/js?id=G-...and the inlinegtag('config', ...).