Le Duy Khuong

Chuỗi: seo-ga4 · Phần 1

Năng suất & công cụ dev

GA4 Architecture & Data Model

Event-based model, UA vs GA4, Next.js integration

2026-03-206 phút đọcVI

GA4 Architecture & Data Model

Mở đầu

Google Analytics 4 (GA4) là phiên bản hiện tại của Google Analytics, thay thế hoàn toàn Universal Analytics (UA) từ tháng 7/2023. Nếu anh đã từng dùng UA, GA4 sẽ cảm thấy rất khác — vì nó được xây dựng lại từ zero với mô hình dữ liệu hoàn toàn mới.

leduykhuong.com tích hợp GA4 qua component GA4Script.tsx. Mục tiêu không chỉ là "biết có bao nhiêu visits" mà là hiểu user behavior: họ đọc bài nào, tìm kiếm gì, ở lại bao lâu, và quay lại không.

Mục tiêu bài này: Hiểu GA4 architecture (data collection → processing → reporting), data model event-based, và cách leduykhuong.com integrate GA4 vào Next.js static export.


Universal Analytics vs GA4 — Sự thay đổi lớn

Khía cạnhUniversal Analytics (cũ)GA4 (hiện tại)
Data modelSession-basedEvent-based
Core metricPageviews, SessionsEvents, Engagement
User identityCookie-based onlyCross-device (User ID + Google Signals)
PrivacyCookie-dependentPrivacy-centric, consent mode
ReportingPre-built reportsExploration (tự build)
EcommerceEnhanced EcommerceBuilt-in ecommerce events
Free tier limit10M hits/monthUnlimited events

Thay đổi quan trọng nhất: Từ session-based sang event-based.

Trong UA: mọi thứ xoay quanh "session" (phiên truy cập). User vào site → session bắt đầu → actions ghi vào session → session kết thúc.

Trong GA4: mọi thứ là event. Page view là event. Click là event. Scroll là event. Session là một derived concept từ events, không phải first-class entity.


Data Model — Everything is an Event

GA4 chỉ có 1 entity type: Event. Mỗi event có:

Event name: "page_view"
Parameters:
  page_location: "https://leduykhuong.com/vi/blog/learning-in-public"
  page_title: "Learning in Public | Le Duy Khuong"
  page_referrer: "https://google.com"
User properties:
  language: "vi"
  device_category: "desktop"

4 loại events

LoạiThu thập bởiVí dụConfig cần?
Automatically collectedGA4 tự độngfirst_visit, session_start, user_engagementKhông
Enhanced measurementGA4 tự động (bật/tắt)page_view, scroll, click, file_downloadBật trong GA4 settings
RecommendedDev implementlogin, sign_up, purchase, searchCó — theo naming convention
CustomDev implementtheme_toggle, language_switchCó — tự define

leduykhuong.com events:

// lib/analytics.ts
export const ANALYTICS_EVENTS = {
  SEARCH_USED: "search_used",        // Custom event
  THEME_TOGGLE: "theme_toggle",      // Custom event
  LANGUAGE_SWITCH: "language_switch", // Custom event
  EXTERNAL_LINK_CLICK: "external_link_click", // Custom event
} as const;

Ngoài 4 custom events này, GA4 tự động track: page_view, scroll, first_visit, session_start.


GA4 Integration — GA4Script.tsx

// components/analytics/GA4Script.tsx
"use client";
 
import Script from "next/script";
 
const GA4_ID = process.env.NEXT_PUBLIC_GA4_ID;
 
export function GA4Script() {
  if (!GA4_ID) return null;
 
  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${GA4_ID}`}
        strategy="afterInteractive"
      />
      <Script id="ga4-init" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${GA4_ID}', {
            anonymize_ip: true,
            send_page_view: true
          });
        `}
      </Script>
    </>
  );
}

Phân tích:

  1. "use client" — GA4 script chạy ở browser, cần client component.

  2. process.env.NEXT_PUBLIC_GA4_ID — Measurement ID (format: G-XXXXXXXXXX). Prefix NEXT_PUBLIC_ = available ở client-side. Nếu env var không có → return null → không load GA4 (dev mode).

  3. strategy="afterInteractive" — Load GA4 SAU khi page đã interactive. Không block initial render — important cho performance (Lighthouse LCP).

  4. window.dataLayer — GA4 dùng dataLayer pattern: events push vào array → gtag.js đọc và gửi lên Google servers.

  5. anonymize_ip: true — IP anonymization. IP user bị truncate trước khi lưu. GDPR/privacy compliance.

  6. send_page_view: true — Tự động gửi page_view event khi config load. Mặc định đã true nhưng explicit declaration rõ ràng hơn.


AnalyticsProvider — Production Gate

// components/analytics/AnalyticsProvider.tsx
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
  const [isProduction, setIsProduction] = useState(false);
 
  useEffect(() => {
    setIsProduction(window.location.hostname === "leduykhuong.com");
  }, []);
 
  if (!isProduction) return <>{children}</>;
 
  return (
    <PostHogProvider>
      <GA4Script />
      {children}
    </PostHogProvider>
  );
}

Tại sao cần production gate?

  1. Không pollute data: Nếu load GA4 trên localhost, mỗi lần dev → thêm fake sessions. Data trở nên unreliable.

  2. Hostname check: window.location.hostname === "leduykhuong.com" — chỉ load analytics trên production domain. Preview deployments (staging URLs) cũng bị skip.

  3. Client-side check: useEffect chạy sau render → SSR không bị ảnh hưởng. useState(false) → default = no analytics → no flash of analytics scripts.


Data Flow — Từ Browser tới Reports

User visits page
    │
    ├─ GA4 gtag.js loads (afterInteractive)
    │
    ├─ Automatic events fire:
    │   ├─ first_visit (new user)
    │   ├─ session_start
    │   └─ page_view (page_location, page_title)
    │
    ├─ Enhanced measurement events:
    │   ├─ scroll (user scrolls 90%)
    │   └─ click (outbound links)
    │
    ├─ Custom events (trackEvent):
    │   ├─ search_used (query: "agentic")
    │   └─ theme_toggle (theme: "dark")
    │
    └─ Events sent to GA4 servers
        │
        ├─ Processing (24-48 hours)
        │
        └─ Available in GA4 Reports

Processing delay: GA4 data không real-time trong standard reports (trừ Realtime report). Thường 24-48 giờ để data đầy đủ. Đây là khác biệt lớn với UA (gần real-time).


trackEvent() — Unified Analytics Function

// lib/analytics.ts
export function trackEvent(
  name: string,
  properties?: Record<string, unknown>
) {
  // GA4
  if (typeof window !== "undefined" && window.gtag) {
    window.gtag("event", name, properties);
  }
  // PostHog
  if (typeof window !== "undefined" && window.posthog) {
    window.posthog.capture(name, properties);
  }
}

Abstraction pattern: trackEvent() gửi event tới CẢ GA4 và PostHog đồng thời. Components chỉ cần gọi 1 function:

// Trong component
import { trackEvent, ANALYTICS_EVENTS } from "@/lib/analytics";
 
trackEvent(ANALYTICS_EVENTS.SEARCH_USED, { query: searchTerm });
trackEvent(ANALYTICS_EVENTS.THEME_TOGGLE, { theme: "dark" });

Safety checks:

  • typeof window !== "undefined" — SSR safe (server không có window)
  • window.gtag — Chỉ fire nếu GA4 đã load
  • window.posthog — Chỉ fire nếu PostHog đã load

Nếu analytics chưa load (dev mode, consent denied) → function no-ops gracefully. Không error, không crash.


GA4 Account Structure

GA4 Account (Google Account level)
└── Property (= 1 website/app)
    ├── Data Stream: Web (leduykhuong.com)
    │   └── Measurement ID: G-XXXXXXXXXX
    ├── Data Stream: iOS App (optional)
    └── Data Stream: Android App (optional)
  • Account — Container cấp cao nhất, thường 1 per organization
  • Property — 1 website hoặc app. leduykhuong.com = 1 property
  • Data Stream — Source of data. Web stream cho website. Mỗi stream có Measurement ID riêng
  • Measurement IDG-XXXXXXXXXX format. Đây là giá trị trong NEXT_PUBLIC_GA4_ID

Thực hành

Bài tập 1: Verify GA4 loading

Mở leduykhuong.com trên Chrome → F12 → Network tab → filter gtag:

  • gtag/js?id=G-XXXX có load không?
  • Status code 200?

Mở localhost:3000 → F12 → Network → filter gtag:

  • gtag có load không? Tại sao?

Bài tập 2: Đọc analytics code

cd ACE-component/ACE-leduykhuong-site
 
# Tìm tất cả nơi gọi trackEvent
grep -rn "trackEvent" --include="*.tsx" --include="*.ts" app/ components/ lib/

Câu hỏi: Bao nhiêu nơi gọi trackEvent? Event nào phổ biến nhất?

Bài tập 3: GA4 Realtime

Nếu có access GA4 property:

  1. Mở GA4 → Reports → Realtime
  2. Mở leduykhuong.com trong tab khác
  3. Quan sát: event page_view xuất hiện trong Realtime?

Tóm tắt

  • GA4 = event-based — Mọi interaction là event (page_view, scroll, click, custom)
  • 4 loại events: automatically collected, enhanced measurement, recommended, custom
  • GA4Script.tsx — Load gtag.js với afterInteractive strategy, config anonymize_ip
  • AnalyticsProvider — Production gate: chỉ load GA4+PostHog trên leduykhuong.com
  • trackEvent() — Unified abstraction gửi event tới cả GA4 và PostHog
  • Processing delay — Standard reports 24-48h, Realtime report gần tức thì

Bài tiếp theo

Bài 10: GA4 Reports — Reading & Interpreting — Cách đọc GA4 reports: Acquisition (traffic từ đâu), Engagement (user làm gì), và Retention (họ có quay lại không). Focus vào metrics quan trọng cho personal blog.

LDK

Le Duy Khuong

AI Transformation & Digital Strategy. Writing about agentic systems, engineering leadership, and building in public.