fix(landing): wire GA4 rollout config (#2615)

Co-authored-by: ashley li <ashleyli@ashleydeMacBook-Air-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ashleyashli 2026-05-22 14:56:58 +08:00 committed by GitHub
parent a0e1b9510b
commit 558fedd207
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 58 additions and 14 deletions

View file

@ -106,6 +106,8 @@ jobs:
run: pnpm --filter @open-design/landing-page previews
- name: Build landing page
env:
PUBLIC_GA_MEASUREMENT_ID: ${{ vars.PUBLIC_GA_MEASUREMENT_ID }}
run: pnpm --filter @open-design/landing-page build
- name: Lint changed blog SEO

View file

@ -87,6 +87,8 @@ jobs:
run: pnpm --filter @open-design/landing-page previews
- name: Build landing page
env:
PUBLIC_GA_MEASUREMENT_ID: ${{ vars.PUBLIC_GA_MEASUREMENT_ID }}
run: pnpm --filter @open-design/landing-page build
- name: Verify zero external JavaScript

View file

@ -1,5 +1,8 @@
---
import { GOOGLE_ANALYTICS_HEAD_HTML } from '../_lib/google-analytics';
import { googleAnalyticsHeadHtml } from '../_lib/google-analytics';
const measurementId = import.meta.env.PUBLIC_GA_MEASUREMENT_ID;
const headHtml = googleAnalyticsHeadHtml(measurementId);
---
<Fragment set:html={GOOGLE_ANALYTICS_HEAD_HTML} />
<Fragment set:html={headHtml} />

View file

@ -19,6 +19,7 @@ import '../sub-pages.css';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import FaviconLinks from './favicon-links.astro';
import GoogleAnalytics from './google-analytics.astro';
import ResourceHints from './resource-hints.astro';
import HeaderEnhancer from './header-enhancer.astro';
import { Header, type HeaderProps } from './header';
@ -79,6 +80,7 @@ const ldArray = jsonLd ? (Array.isArray(jsonLd) ? jsonLd : [jsonLd]) : [];
<FaviconLinks />
<ResourceHints />
<GoogleAnalytics />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Open Design" />

View file

@ -1,22 +1,51 @@
export const GA_MEASUREMENT_ID = 'G-N567SYTTWR';
export const GOOGLE_ANALYTICS_HEAD_HTML = `<!-- Google tag (gtag.js) -->
export function googleAnalyticsHeadHtml(measurementId: string | undefined): string {
if (!measurementId) return '';
return `<!-- Google tag (gtag.js) -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
var gtagScript = document.createElement('script');
gtagScript.async = true;
gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}';
gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id=${measurementId}';
document.head.appendChild(gtagScript);
gtag('js', new Date());
gtag('config', '${GA_MEASUREMENT_ID}');
</script>`;
gtag('config', ${JSON.stringify(measurementId)});
export function injectGoogleAnalytics(html: string): string {
if (html.includes(GA_MEASUREMENT_ID)) return html;
if (html.includes('</head>')) {
return html.replace('</head>', `${GOOGLE_ANALYTICS_HEAD_HTML}\n</head>`);
}
return `${GOOGLE_ANALYTICS_HEAD_HTML}\n${html}`;
document.addEventListener('click', function (event) {
if (typeof gtag !== 'function') return;
var link = event.target && event.target.closest ? event.target.closest('a[href]') : null;
if (!link) return;
var href = link.href;
var label = (link.getAttribute('aria-label') || link.textContent || '').trim().replace(/\\s+/g, ' ');
var lowerHref = href.toLowerCase();
var lowerLabel = label.toLowerCase();
var cta = null;
if (lowerHref.includes('github.com/nexu-io/open-design/releases')) cta = 'download_desktop';
else if (lowerHref === 'https://github.com/nexu-io/open-design' || lowerLabel.includes('star')) cta = 'star_github';
else if (lowerHref.includes('discord.gg/')) cta = 'join_discord';
else if (lowerHref.includes('github.com/nexu-io/open-design/issues')) cta = 'open_issue';
else if (link.pathname && link.pathname.startsWith('/blog/')) cta = 'blog_cta';
else if (link.pathname && link.pathname.startsWith('/tutorials/')) cta = 'tutorial_cta';
if (!cta) return;
gtag('event', 'cta_click', {
cta_name: cta,
link_url: href,
link_text: label.slice(0, 120),
});
});
</script>`;
}
export function injectGoogleAnalytics(html: string, measurementId: string | undefined): string {
const headHtml = googleAnalyticsHeadHtml(measurementId);
if (!headHtml) return html;
if (html.includes(measurementId!)) return html;
if (html.includes('</head>')) {
return html.replace('</head>', `${headHtml}\n</head>`);
}
return `${headHtml}\n${html}`;
}

View file

@ -4,6 +4,7 @@ import '../globals.css';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import FaviconLinks from '../_components/favicon-links.astro';
import GoogleAnalytics from '../_components/google-analytics.astro';
import ResourceHints from '../_components/resource-hints.astro';
import LocaleSwitcherScript from '../_components/locale-switcher-script.astro';
import PreciseLazyload from '../_components/precise-lazyload.astro';
@ -146,6 +147,7 @@ const pageHtml = renderToStaticMarkup(
<FaviconLinks />
<ResourceHints />
<GoogleAnalytics />
{/*
* Hero LCP preload. Cloudflare Pages turns this <link rel="preload"> into

View file

@ -11,6 +11,7 @@
import { getCollection, render } from 'astro:content';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import GoogleAnalytics from '../../_components/google-analytics.astro';
import HeaderEnhancer from '../../_components/header-enhancer.astro';
import { Header, type HeaderProps } from '../../_components/header';
import LocaleSwitcherScript from '../../_components/locale-switcher-script.astro';
@ -126,6 +127,7 @@ const tutorialCopy =
datePublished={entry.data.date}
category={tutorialCopy.category}
/>
<GoogleAnalytics />
</head>
<body>
<div class='shell'>

View file

@ -13,6 +13,7 @@
import { getCollection } from 'astro:content';
import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import GoogleAnalytics from '../../_components/google-analytics.astro';
import HeaderEnhancer from '../../_components/header-enhancer.astro';
import { Header, type HeaderProps } from '../../_components/header';
import LocaleSwitcherScript from '../../_components/locale-switcher-script.astro';
@ -125,6 +126,7 @@ const featuredCopy = featuredTutorial ? localizedTutorial(featuredTutorial) : un
description={seoDescription}
pathname={Astro.url.pathname}
/>
<GoogleAnalytics />
</head>
<body>
<div class='shell'>