Google Analytics 4 untuk Developer: Setup, Custom Events, dan Reporting - Gak Seribet yang Lo Kira!
Panduan lengkap Google Analytics 4 untuk developer. Setup GA4, custom events, enhanced ecommerce, dan reporting yang actionable. Plus tips debugging yang bikin hidup lo lebih mudah.

📊 Google Analytics 4 untuk Developer: Setup, Custom Events, dan Reporting - Gak Seribet yang Lo Kira!
Lo tau gak sih, dulu gue mikir Google Analytics itu cuma urusan marketing team. “Ah, gue kan developer, yang penting website jalan, traffic urusan mereka.” Ternyata… gue salah besar. 😅
Pas pertama kali diminta setup GA4 di project company, gue sampe begadang 3 hari. Dokumentasinya ribet, error message-nya gak jelas, dan yang paling nyebelin: data gak masuk-masuk padahal kode udah bener. Rasanya kayak debug JavaScript yang gak ada console.log-nya.
Sekarang, setelah setup GA4 di puluhan project, gue mau share everything yang gue pelajari. From zero to hero, dengan gaya bahasa yang gak bikin pusing.
“Data is the new oil, but only if you know how to refine it.” - Wise Data Analyst (probably)
🤔 Kenapa Developer Harus Peduli sama GA4?
Sebelum kita dive ke technical stuff, gue mau jelasin dulu kenapa lo sebagai developer harus care sama analytics.
The Reality Check
// Conversation yang sering terjadi:
const clientMeeting = {
client: "Website traffic turun 30% bulan ini, kenapa?",
developer: "Eh... website kan jalan normal, mungkin masalah marketing?",
client: "Tapi kan lo yang bikin website-nya...",
developer: "😰" // Internal panic
};
// Yang seharusnya terjadi:
const betterConversation = {
client: "Website traffic turun 30% bulan ini, kenapa?",
developer: "Let me check GA4... Oh, ada issue di checkout flow. User drop 40% di step 2. Kemarin ada update payment gateway, mungkin ada bug.",
client: "Wah, good catch! Fix dong.",
developer: "😎" // Hero moment
};
Benefits untuk Developer
const developerBenefits = {
debugging: {
realUserData: "Tau user behavior yang real, bukan asumsi",
performanceInsights: "Detect performance issues dari user perspective",
errorTracking: "Track JavaScript errors dan failed interactions"
},
productDecisions: {
featureUsage: "Tau fitur mana yang actually dipake user",
userJourney: "Understand user flow dan pain points",
conversionOptimization: "Data-driven improvements"
},
careerGrowth: {
fullStackMindset: "Gak cuma coding, tapi understand business impact",
dataLiteracy: "Skill yang makin valuable di 2025",
stakeholderCommunication: "Bisa ngomong pake data, bukan feeling"
}
};
🚀 GA4 Setup: From Zero to Tracking
Step 1: Create GA4 Property
// Buat yang belum punya GA4 account
const setupSteps = {
step1: "Go to analytics.google.com",
step2: "Create Account (if needed)",
step3: "Create Property",
step4: "Choose 'Web' platform",
step5: "Get Measurement ID (G-XXXXXXXXXX)"
};
// Pro tip: Bikin separate property untuk dev/staging
const environmentSetup = {
production: "G-PROD123456",
staging: "G-STAGING789",
development: "G-DEV000111" // Optional, bisa skip buat dev
};
Step 2: Install gtag (The Traditional Way)
<!-- Method 1: Direct gtag installation -->
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX', {
// Basic config
page_title: document.title,
page_location: window.location.href,
// Enhanced ecommerce (if needed)
send_page_view: true,
// Custom parameters
custom_map: {
'custom_parameter_1': 'user_type',
'custom_parameter_2': 'subscription_plan'
}
});
</script>
</head>
Step 3: Modern Setup dengan Environment Variables
// config/analytics.js
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID ||
process.env.VITE_GA_MEASUREMENT_ID ||
process.env.REACT_APP_GA_MEASUREMENT_ID;
export const initGA = () => {
// Only initialize in production or staging
if (!GA_MEASUREMENT_ID || process.env.NODE_ENV === 'development') {
console.log('GA4 not initialized - missing ID or dev environment');
return;
}
// Load gtag script
const script = document.createElement('script');
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`;
document.head.appendChild(script);
// Initialize dataLayer
window.dataLayer = window.dataLayer || [];
window.gtag = function() {
window.dataLayer.push(arguments);
};
// Configure GA4
window.gtag('js', new Date());
window.gtag('config', GA_MEASUREMENT_ID, {
page_title: document.title,
page_location: window.location.href,
send_page_view: true,
// Privacy settings
anonymize_ip: true,
allow_google_signals: false, // Set true if you want Google Signals
allow_ad_personalization_signals: false,
// Debug mode (only in non-production)
debug_mode: process.env.NODE_ENV !== 'production'
});
console.log('GA4 initialized with ID:', GA_MEASUREMENT_ID);
};
// Helper function untuk check if GA is loaded
export const isGALoaded = () => {
return typeof window !== 'undefined' &&
typeof window.gtag === 'function' &&
GA_MEASUREMENT_ID;
};
Step 4: React/Next.js Integration
// hooks/useAnalytics.js
import { useEffect } from 'react';
import { useRouter } from 'next/router'; // Next.js
// import { useLocation } from 'react-router-dom'; // React Router
export const useAnalytics = () => {
const router = useRouter();
// const location = useLocation(); // For React Router
useEffect(() => {
// Initialize GA4 on mount
initGA();
}, []);
useEffect(() => {
// Track page views on route change
const handleRouteChange = (url) => {
if (isGALoaded()) {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: url,
page_title: document.title,
page_location: window.location.href
});
}
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
// Return tracking functions
return {
trackEvent: (eventName, parameters = {}) => {
if (isGALoaded()) {
window.gtag('event', eventName, parameters);
}
},
trackPurchase: (transactionData) => {
if (isGALoaded()) {
window.gtag('event', 'purchase', transactionData);
}
},
setUserProperties: (properties) => {
if (isGALoaded()) {
window.gtag('config', GA_MEASUREMENT_ID, {
user_properties: properties
});
}
}
};
};
// App.js atau _app.js
import { useAnalytics } from '../hooks/useAnalytics';
function MyApp({ Component, pageProps }) {
const analytics = useAnalytics();
return <Component {...pageProps} analytics={analytics} />;
}
Step 5: Vue.js Integration
// plugins/analytics.js (Nuxt.js)
export default ({ app, $config }, inject) => {
const GA_ID = $config.gaId;
if (!GA_ID) {
console.warn('GA4 ID not found');
return;
}
// Initialize GA4
const initGA = () => {
const script = document.createElement('script');
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
window.gtag = function() {
window.dataLayer.push(arguments);
};
window.gtag('js', new Date());
window.gtag('config', GA_ID);
};
// Analytics object
const analytics = {
init: initGA,
trackEvent(eventName, parameters = {}) {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', eventName, parameters);
}
},
trackPage(path) {
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('config', GA_ID, {
page_path: path
});
}
}
};
// Inject into Vue instance
inject('analytics', analytics);
// Auto-initialize
if (process.client) {
analytics.init();
}
};
// nuxt.config.js
export default {
plugins: [
{ src: '~/plugins/analytics.js', mode: 'client' }
],
publicRuntimeConfig: {
gaId: process.env.GA_MEASUREMENT_ID
}
};
📈 Custom Events: Track What Matters
Ini bagian yang paling penting dan sering diabaikan. Default GA4 tracking cuma ngasih basic page views. Real insights datang dari custom events.
Event Naming Convention
// ❌ Bad naming - gak konsisten
gtag('event', 'click');
gtag('event', 'Button Click');
gtag('event', 'user_signup');
gtag('event', 'PURCHASE_COMPLETED');
// ✅ Good naming - konsisten dan descriptive
gtag('event', 'button_click', {
button_name: 'cta_signup',
page_section: 'hero'
});
gtag('event', 'user_signup', {
signup_method: 'email',
user_type: 'free_trial'
});
gtag('event', 'purchase_completed', {
transaction_id: 'TXN_123456',
value: 99.99,
currency: 'USD'
});
Essential Events untuk Web App
// User engagement events
const trackUserEngagement = {
// Scroll tracking
trackScroll: (percentage) => {
gtag('event', 'scroll', {
scroll_depth: percentage,
page_path: window.location.pathname
});
},
// Time on page
trackTimeOnPage: (seconds) => {
gtag('event', 'time_on_page', {
engagement_time_msec: seconds * 1000,
page_path: window.location.pathname
});
},
// File downloads
trackDownload: (fileName, fileType) => {
gtag('event', 'file_download', {
file_name: fileName,
file_extension: fileType,
link_url: window.location.href
});
},
// External link clicks
trackExternalLink: (url) => {
gtag('event', 'click', {
link_url: url,
link_domain: new URL(url).hostname,
outbound: true
});
}
};
// Form tracking
const trackForms = {
formStart: (formName) => {
gtag('event', 'form_start', {
form_name: formName,
page_path: window.location.pathname
});
},
formSubmit: (formName, success = true) => {
gtag('event', 'form_submit', {
form_name: formName,
success: success,
page_path: window.location.pathname
});
},
formError: (formName, errorField, errorMessage) => {
gtag('event', 'form_error', {
form_name: formName,
error_field: errorField,
error_message: errorMessage
});
}
};
// Search tracking
const trackSearch = {
searchPerformed: (query, resultsCount) => {
gtag('event', 'search', {
search_term: query,
search_results: resultsCount,
page_path: window.location.pathname
});
},
searchResultClick: (query, resultPosition, resultUrl) => {
gtag('event', 'select_content', {
content_type: 'search_result',
search_term: query,
content_id: resultUrl,
index: resultPosition
});
}
};
E-commerce Events (Enhanced Ecommerce)
// E-commerce tracking yang comprehensive
const ecommerceTracking = {
// View item
viewItem: (item) => {
gtag('event', 'view_item', {
currency: 'USD',
value: item.price,
items: [{
item_id: item.id,
item_name: item.name,
item_category: item.category,
item_variant: item.variant,
price: item.price,
quantity: 1
}]
});
},
// Add to cart
addToCart: (item, quantity = 1) => {
gtag('event', 'add_to_cart', {
currency: 'USD',
value: item.price * quantity,
items: [{
item_id: item.id,
item_name: item.name,
item_category: item.category,
price: item.price,
quantity: quantity
}]
});
},
// Remove from cart
removeFromCart: (item, quantity = 1) => {
gtag('event', 'remove_from_cart', {
currency: 'USD',
value: item.price * quantity,
items: [{
item_id: item.id,
item_name: item.name,
item_category: item.category,
price: item.price,
quantity: quantity
}]
});
},
// Begin checkout
beginCheckout: (cartItems, cartValue) => {
gtag('event', 'begin_checkout', {
currency: 'USD',
value: cartValue,
items: cartItems.map(item => ({
item_id: item.id,
item_name: item.name,
item_category: item.category,
price: item.price,
quantity: item.quantity
}))
});
},
// Purchase
purchase: (transactionData) => {
gtag('event', 'purchase', {
transaction_id: transactionData.id,
value: transactionData.total,
currency: 'USD',
tax: transactionData.tax,
shipping: transactionData.shipping,
items: transactionData.items.map(item => ({
item_id: item.id,
item_name: item.name,
item_category: item.category,
price: item.price,
quantity: item.quantity
}))
});
},
// Refund
refund: (transactionId, refundValue, refundedItems = []) => {
gtag('event', 'refund', {
transaction_id: transactionId,
value: refundValue,
currency: 'USD',
items: refundedItems.map(item => ({
item_id: item.id,
quantity: item.quantity
}))
});
}
};
Real-World Implementation Example
// Contoh implementasi di React component
import React, { useEffect } from 'react';
import { useAnalytics } from '../hooks/useAnalytics';
const ProductPage = ({ product }) => {
const { trackEvent } = useAnalytics();
useEffect(() => {
// Track product view
trackEvent('view_item', {
currency: 'USD',
value: product.price,
items: [{
item_id: product.id,
item_name: product.name,
item_category: product.category,
price: product.price,
quantity: 1
}]
});
}, [product.id]);
const handleAddToCart = () => {
// Add to cart logic
addToCart(product);
// Track add to cart
trackEvent('add_to_cart', {
currency: 'USD',
value: product.price,
items: [{
item_id: product.id,
item_name: product.name,
item_category: product.category,
price: product.price,
quantity: 1
}]
});
};
const handleShareProduct = (platform) => {
// Share logic
shareProduct(product, platform);
// Track share
trackEvent('share', {
method: platform,
content_type: 'product',
content_id: product.id,
item_id: product.id
});
};
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
<button onClick={handleAddToCart}>
Add to Cart
</button>
<button onClick={() => handleShareProduct('facebook')}>
Share on Facebook
</button>
<button onClick={() => handleShareProduct('twitter')}>
Share on Twitter
</button>
</div>
);
};
🔍 Advanced Tracking Techniques
User Properties & Custom Dimensions
// Set user properties untuk segmentation
const setUserProperties = (user) => {
gtag('config', GA_MEASUREMENT_ID, {
user_properties: {
user_type: user.subscription_plan, // 'free', 'premium', 'enterprise'
signup_date: user.signup_date,
user_ltv: user.lifetime_value,
preferred_language: user.language,
user_segment: user.segment, // 'new', 'returning', 'power_user'
company_size: user.company_size // For B2B apps
}
});
};
// Custom parameters untuk events
const trackWithCustomDimensions = (eventName, baseParams, customDimensions) => {
gtag('event', eventName, {
...baseParams,
// Custom dimensions
custom_parameter_1: customDimensions.feature_flag,
custom_parameter_2: customDimensions.ab_test_variant,
custom_parameter_3: customDimensions.user_cohort,
custom_parameter_4: customDimensions.traffic_source
});
};
// Example usage
trackWithCustomDimensions('button_click', {
button_name: 'upgrade_cta',
page_section: 'pricing'
}, {
feature_flag: 'new_pricing_ui',
ab_test_variant: 'variant_b',
user_cohort: 'march_2025',
traffic_source: 'google_ads'
});
Error Tracking
// JavaScript error tracking
const trackJavaScriptErrors = () => {
window.addEventListener('error', (event) => {
gtag('event', 'exception', {
description: `${event.error.name}: ${event.error.message}`,
fatal: false,
error_file: event.filename,
error_line: event.lineno,
error_column: event.colno,
stack_trace: event.error.stack
});
});
// Promise rejection tracking
window.addEventListener('unhandledrejection', (event) => {
gtag('event', 'exception', {
description: `Unhandled Promise Rejection: ${event.reason}`,
fatal: false,
error_type: 'promise_rejection'
});
});
};
// API error tracking
const trackAPIErrors = (endpoint, method, statusCode, errorMessage) => {
gtag('event', 'api_error', {
api_endpoint: endpoint,
http_method: method,
status_code: statusCode,
error_message: errorMessage,
page_path: window.location.pathname
});
};
// Usage example
fetch('/api/users')
.then(response => {
if (!response.ok) {
trackAPIErrors('/api/users', 'GET', response.status, 'Failed to fetch users');
throw new Error('API Error');
}
return response.json();
})
.catch(error => {
trackAPIErrors('/api/users', 'GET', 0, error.message);
});
Performance Tracking
// Core Web Vitals tracking
const trackWebVitals = () => {
// Largest Contentful Paint
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
gtag('event', 'web_vitals', {
metric_name: 'LCP',
metric_value: Math.round(lastEntry.startTime),
metric_rating: lastEntry.startTime <= 2500 ? 'good' :
lastEntry.startTime <= 4000 ? 'needs_improvement' : 'poor'
});
}).observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
gtag('event', 'web_vitals', {
metric_name: 'FID',
metric_value: Math.round(entry.processingStart - entry.startTime),
metric_rating: entry.processingStart - entry.startTime <= 100 ? 'good' :
entry.processingStart - entry.startTime <= 300 ? 'needs_improvement' : 'poor'
});
});
}).observe({ entryTypes: ['first-input'] });
// Cumulative Layout Shift
let clsValue = 0;
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
gtag('event', 'web_vitals', {
metric_name: 'CLS',
metric_value: Math.round(clsValue * 1000) / 1000,
metric_rating: clsValue <= 0.1 ? 'good' :
clsValue <= 0.25 ? 'needs_improvement' : 'poor'
});
}).observe({ entryTypes: ['layout-shift'] });
};
// Page load performance
const trackPagePerformance = () => {
window.addEventListener('load', () => {
setTimeout(() => {
const navigation = performance.getEntriesByType('navigation')[0];
gtag('event', 'page_performance', {
dns_time: Math.round(navigation.domainLookupEnd - navigation.domainLookupStart),
connect_time: Math.round(navigation.connectEnd - navigation.connectStart),
response_time: Math.round(navigation.responseEnd - navigation.requestStart),
dom_load_time: Math.round(navigation.domContentLoadedEventEnd - navigation.navigationStart),
window_load_time: Math.round(navigation.loadEventEnd - navigation.navigationStart)
});
}, 0);
});
};
🛠️ Debugging & Testing
GA4 Debug Mode
// Enable debug mode
gtag('config', GA_MEASUREMENT_ID, {
debug_mode: true
});
// Custom debug helper
const debugGA = {
isDebugMode: () => {
return new URLSearchParams(window.location.search).has('ga_debug') ||
localStorage.getItem('ga_debug') === 'true';
},
enableDebug: () => {
localStorage.setItem('ga_debug', 'true');
console.log('GA4 Debug mode enabled');
},
disableDebug: () => {
localStorage.removeItem('ga_debug');
console.log('GA4 Debug mode disabled');
},
logEvent: (eventName, parameters) => {
if (debugGA.isDebugMode()) {
console.group(`🔍 GA4 Event: ${eventName}`);
console.log('Parameters:', parameters);
console.log('Timestamp:', new Date().toISOString());
console.groupEnd();
}
}
};
// Enhanced tracking function dengan debug
const trackEventWithDebug = (eventName, parameters = {}) => {
debugGA.logEvent(eventName, parameters);
if (isGALoaded()) {
gtag('event', eventName, parameters);
} else {
console.warn('GA4 not loaded, event not sent:', eventName);
}
};
Testing Tools
// GA4 Event Validator
const validateEvent = (eventName, parameters) => {
const errors = [];
// Check event name
if (!eventName || typeof eventName !== 'string') {
errors.push('Event name is required and must be a string');
}
if (eventName.length > 40) {
errors.push('Event name must be 40 characters or less');
}
// Check parameters
if (parameters) {
Object.keys(parameters).forEach(key => {
if (key.length > 40) {
errors.push(`Parameter name "${key}" must be 40 characters or less`);
}
if (typeof parameters[key] === 'string' && parameters[key].length > 100) {
errors.push(`Parameter value for "${key}" must be 100 characters or less`);
}
});
if (Object.keys(parameters).length > 25) {
errors.push('Maximum 25 custom parameters allowed per event');
}
}
return {
isValid: errors.length === 0,
errors: errors
};
};
// Usage
const validation = validateEvent('purchase', {
transaction_id: 'TXN_123456',
value: 99.99,
currency: 'USD'
});
if (!validation.isValid) {
console.error('Event validation failed:', validation.errors);
} else {
trackEventWithDebug('purchase', parameters);
}
Real-Time Testing
// Real-time event monitor
class GA4Monitor {
constructor() {
this.events = [];
this.isMonitoring = false;
}
start() {
this.isMonitoring = true;
this.interceptGtag();
console.log('🔍 GA4 Monitor started');
}
stop() {
this.isMonitoring = false;
console.log('🔍 GA4 Monitor stopped');
}
interceptGtag() {
const originalGtag = window.gtag;
window.gtag = (...args) => {
if (this.isMonitoring && args[0] === 'event') {
this.logEvent(args[1], args[2]);
}
return originalGtag.apply(window, args);
};
}
logEvent(eventName, parameters) {
const event = {
name: eventName,
parameters: parameters,
timestamp: new Date().toISOString(),
page: window.location.pathname
};
this.events.push(event);
console.group(`📊 GA4 Event: ${eventName}`);
console.log('Parameters:', parameters);
console.log('Page:', window.location.pathname);
console.log('Time:', event.timestamp);
console.groupEnd();
}
getEvents() {
return this.events;
}
exportEvents() {
const dataStr = JSON.stringify(this.events, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `ga4-events-${Date.now()}.json`;
link.click();
}
}
// Usage
const monitor = new GA4Monitor();
monitor.start();
// Stop monitoring and export events
// monitor.stop();
// monitor.exportEvents();
📊 Reporting & Data Analysis
Custom Reports dengan GA4 API
// GA4 Reporting API integration
class GA4Reporter {
constructor(propertyId, credentials) {
this.propertyId = propertyId;
this.credentials = credentials;
this.baseUrl = 'https://analyticsdata.googleapis.com/v1beta';
}
async getAccessToken() {
// Implement OAuth2 or Service Account authentication
// This is simplified - use proper auth in production
return this.credentials.access_token;
}
async runReport(reportRequest) {
const token = await this.getAccessToken();
const response = await fetch(
`${this.baseUrl}/properties/${this.propertyId}:runReport`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(reportRequest)
}
);
if (!response.ok) {
throw new Error(`GA4 API error: ${response.status}`);
}
return await response.json();
}
// Get basic metrics
async getBasicMetrics(startDate, endDate) {
const reportRequest = {
dimensions: [
{ name: 'date' },
{ name: 'pagePath' }
],
metrics: [
{ name: 'sessions' },
{ name: 'users' },
{ name: 'pageviews' },
{ name: 'bounceRate' },
{ name: 'sessionDuration' }
],
dateRanges: [{
startDate: startDate,
endDate: endDate
}],
orderBys: [{
dimension: { dimensionName: 'date' },
desc: true
}]
};
return await this.runReport(reportRequest);
}
// Get conversion data
async getConversionData(startDate, endDate) {
const reportRequest = {
dimensions: [
{ name: 'eventName' },
{ name: 'source' },
{ name: 'medium' }
],
metrics: [
{ name: 'eventCount' },
{ name: 'conversions' },
{ name: 'totalRevenue' }
],
dateRanges: [{
startDate: startDate,
endDate: endDate
}],
dimensionFilter: {
filter: {
fieldName: 'eventName',
inListFilter: {
values: ['purchase', 'sign_up', 'subscribe']
}
}
}
};
return await this.runReport(reportRequest);
}
// Get user behavior flow
async getUserFlow(startDate, endDate) {
const reportRequest = {
dimensions: [
{ name: 'pagePath' },
{ name: 'previousPagePath' }
],
metrics: [
{ name: 'sessions' },
{ name: 'users' }
],
dateRanges: [{
startDate: startDate,
endDate: endDate
}],
orderBys: [{
metric: { metricName: 'sessions' },
desc: true
}],
limit: 100
};
return await this.runReport(reportRequest);
}
}
// Usage example
const reporter = new GA4Reporter('properties/123456789', {
access_token: 'your-access-token'
});
// Get last 30 days data
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
.toISOString().split('T')[0];
const today = new Date().toISOString().split('T')[0];
reporter.getBasicMetrics(thirtyDaysAgo, today)
.then(data => {
console.log('Basic metrics:', data);
})
.catch(error => {
console.error('Error fetching data:', error);
});
Dashboard Implementation
// Simple dashboard untuk monitoring GA4 data
class GA4Dashboard {
constructor(containerId, reporter) {
this.container = document.getElementById(containerId);
this.reporter = reporter;
this.refreshInterval = 5 * 60 * 1000; // 5 minutes
}
async init() {
await this.loadData();
this.render();
this.startAutoRefresh();
}
async loadData() {
const endDate = new Date().toISOString().split('T')[0];
const startDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
.toISOString().split('T')[0];
try {
this.data = {
metrics: await this.reporter.getBasicMetrics(startDate, endDate),
conversions: await this.reporter.getConversionData(startDate, endDate),
userFlow: await this.reporter.getUserFlow(startDate, endDate)
};
} catch (error) {
console.error('Error loading dashboard data:', error);
this.data = null;
}
}
render() {
if (!this.data) {
this.container.innerHTML = '<div class="error">Error loading data</div>';
return;
}
const metrics = this.processMetrics(this.data.metrics);
const conversions = this.processConversions(this.data.conversions);
this.container.innerHTML = `
<div class="dashboard">
<div class="metrics-grid">
<div class="metric-card">
<h3>Total Users</h3>
<div class="metric-value">${metrics.totalUsers.toLocaleString()}</div>
<div class="metric-change ${metrics.userGrowth >= 0 ? 'positive' : 'negative'}">
${metrics.userGrowth >= 0 ? '+' : ''}${metrics.userGrowth.toFixed(1)}%
</div>
</div>
<div class="metric-card">
<h3>Total Sessions</h3>
<div class="metric-value">${metrics.totalSessions.toLocaleString()}</div>
<div class="metric-change ${metrics.sessionGrowth >= 0 ? 'positive' : 'negative'}">
${metrics.sessionGrowth >= 0 ? '+' : ''}${metrics.sessionGrowth.toFixed(1)}%
</div>
</div>
<div class="metric-card">
<h3>Bounce Rate</h3>
<div class="metric-value">${metrics.bounceRate.toFixed(1)}%</div>
<div class="metric-change ${metrics.bounceRateChange <= 0 ? 'positive' : 'negative'}">
${metrics.bounceRateChange >= 0 ? '+' : ''}${metrics.bounceRateChange.toFixed(1)}%
</div>
</div>
<div class="metric-card">
<h3>Avg Session Duration</h3>
<div class="metric-value">${this.formatDuration(metrics.avgSessionDuration)}</div>
<div class="metric-change ${metrics.durationChange >= 0 ? 'positive' : 'negative'}">
${metrics.durationChange >= 0 ? '+' : ''}${metrics.durationChange.toFixed(1)}%
</div>
</div>
</div>
<div class="conversions-section">
<h3>Top Conversions (Last 7 Days)</h3>
<div class="conversions-list">
${conversions.map(conv => `
<div class="conversion-item">
<span class="event-name">${conv.eventName}</span>
<span class="event-count">${conv.count.toLocaleString()}</span>
<span class="event-revenue">$${conv.revenue.toLocaleString()}</span>
</div>
`).join('')}
</div>
</div>
<div class="chart-section">
<h3>Daily Users Trend</h3>
<canvas id="users-chart" width="400" height="200"></canvas>
</div>
</div>
`;
this.renderChart(metrics.dailyData);
}
processMetrics(rawData) {
// Process raw GA4 data into dashboard metrics
const rows = rawData.rows || [];
let totalUsers = 0;
let totalSessions = 0;
let totalBounceRate = 0;
let totalDuration = 0;
const dailyData = [];
rows.forEach(row => {
const date = row.dimensionValues[0].value;
const users = parseInt(row.metricValues[1].value);
const sessions = parseInt(row.metricValues[0].value);
const bounceRate = parseFloat(row.metricValues[3].value);
const duration = parseFloat(row.metricValues[4].value);
totalUsers += users;
totalSessions += sessions;
totalBounceRate += bounceRate;
totalDuration += duration;
dailyData.push({
date: date,
users: users,
sessions: sessions
});
});
return {
totalUsers: totalUsers,
totalSessions: totalSessions,
bounceRate: totalBounceRate / rows.length,
avgSessionDuration: totalDuration / rows.length,
userGrowth: this.calculateGrowth(dailyData, 'users'),
sessionGrowth: this.calculateGrowth(dailyData, 'sessions'),
bounceRateChange: 0, // Calculate based on previous period
durationChange: 0, // Calculate based on previous period
dailyData: dailyData
};
}
processConversions(rawData) {
const rows = rawData.rows || [];
return rows.map(row => ({
eventName: row.dimensionValues[0].value,
source: row.dimensionValues[1].value,
medium: row.dimensionValues[2].value,
count: parseInt(row.metricValues[0].value),
conversions: parseInt(row.metricValues[1].value),
revenue: parseFloat(row.metricValues[2].value || 0)
})).sort((a, b) => b.conversions - a.conversions);
}
calculateGrowth(data, metric) {
if (data.length < 2) return 0;
const recent = data.slice(-3).reduce((sum, day) => sum + day[metric], 0) / 3;
const previous = data.slice(0, 3).reduce((sum, day) => sum + day[metric], 0) / 3;
return previous > 0 ? ((recent - previous) / previous) * 100 : 0;
}
formatDuration(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}
renderChart(dailyData) {
// Simple chart implementation (you can use Chart.js or similar)
const canvas = document.getElementById('users-chart');
const ctx = canvas.getContext('2d');
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw simple line chart
const maxUsers = Math.max(...dailyData.map(d => d.users));
const padding = 40;
const chartWidth = canvas.width - 2 * padding;
const chartHeight = canvas.height - 2 * padding;
ctx.strokeStyle = '#007bff';
ctx.lineWidth = 2;
ctx.beginPath();
dailyData.forEach((day, index) => {
const x = padding + (index / (dailyData.length - 1)) * chartWidth;
const y = padding + (1 - day.users / maxUsers) * chartHeight;
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
}
startAutoRefresh() {
setInterval(async () => {
await this.loadData();
this.render();
}, this.refreshInterval);
}
}
// Usage
const dashboard = new GA4Dashboard('dashboard-container', reporter);
dashboard.init();
🎯 Best Practices & Common Pitfalls
Do’s and Don’ts
// ✅ DO: Consistent event naming
const goodEventNaming = {
pattern: 'object_action',
examples: [
'button_click',
'form_submit',
'video_play',
'product_view',
'user_signup'
]
};
// ❌ DON'T: Inconsistent naming
const badEventNaming = [
'click',
'Button Click',
'form_submission',
'PRODUCT_VIEWED',
'userSignUp'
];
// ✅ DO: Meaningful parameters
gtag('event', 'purchase', {
transaction_id: 'TXN_123456',
value: 99.99,
currency: 'USD',
items: [{
item_id: 'SKU_001',
item_name: 'Premium Plan',
item_category: 'Subscription',
quantity: 1,
price: 99.99
}]
});
// ❌ DON'T: Vague parameters
gtag('event', 'purchase', {
id: '123',
amount: 99.99
});
// ✅ DO: Respect data privacy
const privacyCompliantTracking = {
anonymizeIP: true,
respectDoNotTrack: true,
cookieConsent: true,
dataRetention: '26 months', // GA4 default
userDeletion: 'implemented' // GDPR compliance
};
// ❌ DON'T: Track PII
gtag('event', 'user_signup', {
email: '[email protected]', // DON'T DO THIS
phone: '+1234567890', // DON'T DO THIS
name: 'John Doe' // DON'T DO THIS
});
Performance Optimization
// Optimize GA4 loading untuk better performance
const optimizeGA4Performance = {
// 1. Load GA4 asynchronously
asyncLoading: () => {
const script = document.createElement('script');
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`;
document.head.appendChild(script);
},
// 2. Batch events when possible
eventBatching: (() => {
let eventQueue = [];
let batchTimeout;
return (eventName, parameters) => {
eventQueue.push({ eventName, parameters });
clearTimeout(batchTimeout);
batchTimeout = setTimeout(() => {
// Send batched events
eventQueue.forEach(event => {
gtag('event', event.eventName, event.parameters);
});
eventQueue = [];
}, 100); // 100ms delay
};
})(),
// 3. Use requestIdleCallback for non-critical tracking
idleTracking: (eventName, parameters) => {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
gtag('event', eventName, parameters);
});
} else {
setTimeout(() => {
gtag('event', eventName, parameters);
}, 0);
}
},
// 4. Implement sampling for high-volume events
sampledTracking: (eventName, parameters, sampleRate = 0.1) => {
if (Math.random() < sampleRate) {
gtag('event', eventName, {
...parameters,
sampled: true,
sample_rate: sampleRate
});
}
}
};
🔗 Integration dengan Tools Lain
GA4 + Google Tag Manager
// GTM integration untuk advanced tracking
const gtmIntegration = {
// Setup GTM with GA4
setupGTM: (gtmId, ga4Id) => {
// GTM script
(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', gtmId);
// Configure GA4 through GTM
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'gtm.start': new Date().getTime(),
'event': 'gtm.js'
});
},
// Push custom events to dataLayer
pushEvent: (eventName, eventData) => {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': eventName,
...eventData
});
},
// Enhanced ecommerce through GTM
pushEcommerce: (action, ecommerceData) => {
window.dataLayer.push({
'event': 'ecommerce_event',
'ecommerce_action': action,
'ecommerce': ecommerceData
});
}
};
// Usage
gtmIntegration.pushEvent('custom_signup', {
'signup_method': 'email',
'user_type': 'premium',
'campaign_source': 'google_ads'
});
GA4 + Data Studio/Looker Studio
// Prepare data untuk Data Studio reporting
const dataStudioIntegration = {
// Custom dimensions untuk better reporting
setupCustomDimensions: () => {
gtag('config', GA_MEASUREMENT_ID, {
'custom_map': {
'custom_parameter_1': 'user_segment',
'custom_parameter_2': 'traffic_source',
'custom_parameter_3': 'device_category',
'custom_parameter_4': 'experiment_variant'
}
});
},
// Consistent event structure untuk reporting
trackForReporting: (category, action, label, value) => {
gtag('event', action, {
'event_category': category,
'event_label': label,
'value': value,
'custom_parameter_1': getUserSegment(),
'custom_parameter_2': getTrafficSource(),
'custom_parameter_3': getDeviceCategory(),
'custom_parameter_4': getExperimentVariant()
});
}
};
🎯 Kesimpulan
GA4 memang initially overwhelming, tapi setelah lo understand the concepts dan implement dengan benar, dia bisa jadi powerful tool buat understand user behavior dan optimize product.
Key Takeaways:
- Start Simple - Mulai dengan basic page views, lalu gradually add custom events
- Be Consistent - Pake naming convention yang konsisten buat events dan parameters
- Think User-Centric - Focus pada user journey, bukan cuma page views
- Respect Privacy - Always comply dengan data privacy regulations
- Test Everything - Use debug mode dan validate events sebelum production
- Automate Reporting - Build dashboards dan automated reports buat stakeholders
Action Plan:
Week 1: Setup basic GA4 tracking Week 2: Implement custom events untuk key user actions Week 3: Add enhanced ecommerce tracking (if applicable) Week 4: Build reporting dashboard dan setup automated alerts
GA4 bukan cuma tool buat marketing team - dia essential buat developer yang mau build data-driven products. Start implementing sekarang, dan lo bakal surprised betapa valuable-nya insights yang lo dapet!
🔗 Artikel Terkait:
- Technical SEO 2025: Schema Markup, Core Web Vitals, dan Page Experience
- A/B Testing untuk Developer: Optimizely vs Google Optimize vs Split
- Core Web Vitals 2025: Panduan Optimasi Google Ranking
Ditulis dengan ❤️ (dan banyak trial & error) oleh Hilal Technologic