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ạnh | Universal Analytics (cũ) | GA4 (hiện tại) |
|---|---|---|
| Data model | Session-based | Event-based |
| Core metric | Pageviews, Sessions | Events, Engagement |
| User identity | Cookie-based only | Cross-device (User ID + Google Signals) |
| Privacy | Cookie-dependent | Privacy-centric, consent mode |
| Reporting | Pre-built reports | Exploration (tự build) |
| Ecommerce | Enhanced Ecommerce | Built-in ecommerce events |
| Free tier limit | 10M hits/month | Unlimited 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ại | Thu thập bởi | Ví dụ | Config cần? |
|---|---|---|---|
| Automatically collected | GA4 tự động | first_visit, session_start, user_engagement | Không |
| Enhanced measurement | GA4 tự động (bật/tắt) | page_view, scroll, click, file_download | Bật trong GA4 settings |
| Recommended | Dev implement | login, sign_up, purchase, search | Có — theo naming convention |
| Custom | Dev implement | theme_toggle, language_switch | Có — 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:
-
"use client"— GA4 script chạy ở browser, cần client component. -
process.env.NEXT_PUBLIC_GA4_ID— Measurement ID (format:G-XXXXXXXXXX). PrefixNEXT_PUBLIC_= available ở client-side. Nếu env var không có → return null → không load GA4 (dev mode). -
strategy="afterInteractive"— Load GA4 SAU khi page đã interactive. Không block initial render — important cho performance (Lighthouse LCP). -
window.dataLayer— GA4 dùng dataLayer pattern: events push vào array → gtag.js đọc và gửi lên Google servers. -
anonymize_ip: true— IP anonymization. IP user bị truncate trước khi lưu. GDPR/privacy compliance. -
send_page_view: true— Tự động gửipage_viewevent 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?
-
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.
-
Hostname check:
window.location.hostname === "leduykhuong.com"— chỉ load analytics trên production domain. Preview deployments (staging URLs) cũng bị skip. -
Client-side check:
useEffectchạ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 đã loadwindow.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 ID —
G-XXXXXXXXXXformat. Đây là giá trị trongNEXT_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-XXXXcó 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:
- Mở GA4 → Reports → Realtime
- Mở leduykhuong.com trong tab khác
- Quan sát: event
page_viewxuấ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ớiafterInteractivestrategy, configanonymize_ipAnalyticsProvider— Production gate: chỉ load GA4+PostHog trênleduykhuong.comtrackEvent()— 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.