GA 입문 3편 — 측정 통합 4 방법 깊이 (gtag · GTM · Firebase · MP)

2026-05-17Google Analytics 입문에서 운영까지

GA 입문 3편. GA4 의 4 측정 방법 깊이 — gtag.js (dataLayer 메커니즘 · command 4종 · 직접 통합), Google Tag Manager (Container · Tag · Trigger · Variable · Workspace · Version · Environment · Preview · sGTM), Firebase SDK (iOS · Android · Flutter · 모바일 자동 event), Measurement Protocol (HTTP endpoint · client_id 결합 · validation server · debug · server-side hit). 결정 매트릭스 + 함정까지 풀어쓴 학습 노트.

📚 Google Analytics 입문에서 운영까지 · 3편 — 측정 통합 4 방법 깊이 (gtag · GTM · Firebase · MP)

이 글은 Google Analytics 입문에서 운영까지 시리즈 3편이에요. 1편 큰 그림4 측정 방법 을 짧게 봤다면, 2편 데이터 모델 다음 — 각 방법의 실전 깊이.

이번 글의 범위

GA 에 데이터 보내는 4 가지 길 의 깊이 풀이. 우리 stack 의 어디서 측정해야 하는지의 결정.

방법 어디서 누구 운영
gtag.js 웹 (코드 직접) 개발자
GTM 웹 (대시보드) 마케터 + 개발자
Firebase SDK 모바일 앱 개발자
Measurement Protocol 서버 백엔드 개발자

gtag.js — 직접 통합

dataLayer 메커니즘

gtag 는 내부적으로 dataLayer (페이지에 둔 글로벌 배열) 를 씁니다. 모든 GA hit 가 dataLayer 에 push 돼요.

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){ dataLayer.push(arguments); }
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXX');
</script>

gtag 함수의 본질dataLayer.push 의 wrapper. 별도 magic 없음.

gtag command 4종

// 1. config — Property 초기화 (앱 시작 시 1회)
gtag('config', 'G-XXXXXXXX', {
  send_page_view: false,    // 자동 page_view 끄기
  user_id: 'user-12345'
});

// 2. event — 이벤트 박기
gtag('event', 'product_viewed', {
  product_id: 'P-123',
  category: 'shoes'
});

// 3. set — Default parameter (이후 모든 event 에 적용)
gtag('set', 'user_properties', {
  user_tier: 'premium'
});

// 4. consent — Consent Mode (2편)
gtag('consent', 'update', {
  analytics_storage: 'granted'
});

config 의 옵션

gtag('config', 'G-XXXXXXXX', {
  send_page_view: true,         // 자동 page_view (default true)
  cookie_domain: 'auto',         // cookie 도메인
  cookie_flags: 'SameSite=None; Secure',  // SameSite (cross-site 시)
  cookie_expires: 63072000,      // cookie 만료 (초, default 2년)
  anonymize_ip: true,            // IP 익명화 (GA4 자동)
  allow_google_signals: true,    // Google Signal 활성
  allow_ad_personalization_signals: true
});

SPA (Single Page App) 의 함정

여기서 시험 함정이 하나 있어요. SPA (React · Vue · Next.js) 는 URL 변경full page reload 없이 일어나요. 그래서 자동 page_view event최초 1회만 박힙니다.

해결 — 수동 page_view:

// React Router · Next.js 의 route change hook
function trackPageView(url) {
  gtag('event', 'page_view', {
    page_location: url,
    page_title: document.title
  });
}

// React Router 예
useEffect(() => {
  trackPageView(window.location.href);
}, [location]);

또는 gtag config 의 send_page_view: false 로 자동을 끄고 모든 page_view 를 수동 으로 박는 방식.

Google Tag Manager (GTM) — 대시보드 운영

GTM 의 구조

[GTM Account]
   ↓
[Container] — 하나의 사이트/앱
   ├─ [Tag] — 발사할 코드 (GA4 · Facebook Pixel · Hotjar 등)
   ├─ [Trigger] — Tag 발사 조건
   ├─ [Variable] — Tag·Trigger 안에서 쓰는 값
   └─ [DataLayer] — 사이트에서 GTM 으로 전달하는 데이터

Container = 태그 관리 설정의 저장소. — 공식 docs

Tag — 발사할 코드

Tag 종류 예:
  - Google Analytics: GA4 Event
  - Google Ads: Conversion Tracking
  - Facebook Pixel
  - Hotjar Tracking
  - Custom HTML
  - Custom JavaScript

수많은 vendor 의 표준 Tag 템플릿. 코드 한 줄 안 박고 마케터가 직접 설정.

Trigger — 발사 조건

대표 Trigger:
  - Page View (모든 페이지뷰)
  - Page View — DOM Ready
  - Page View — Window Loaded
  - Click — All Elements
  - Click — Just Links
  - Form Submission
  - Scroll Depth (25% · 50% · 75% · 90%)
  - Element Visibility
  - YouTube Video
  - Custom Event (dataLayer push)
  - JavaScript Error
  - Timer (N 초 후)

각 Trigger 가 조건 을 정의. 매칭 시 해당 Tag 발사.

Variable — 동적 값

Built-in Variables (자동 제공):
  - Page URL · Page Hostname · Page Path
  - Click URL · Click Text · Click ID · Click Classes
  - Form ID · Form URL
  - Random Number · Event

User-defined Variables (직접 정의):
  - DataLayer Variable (dataLayer 에서 값 추출)
  - JavaScript Variable (window.xxx)
  - Constant
  - Lookup Table (a → b 매핑)
  - Regex Table
  - Custom JavaScript (함수 정의)

대표 사용 — Tag · Trigger 안에서 {{Page Path}} · {{Click Text}} 같이 동적 참조.

DataLayer — GTM 의 데이터 입력

// 사이트 코드에서 GTM 로 데이터 전달
window.dataLayer = window.dataLayer || [];
dataLayer.push({
  event: 'purchase',         // GTM 의 Trigger 가 이걸 듣는다
  transaction_id: 'T-12345',
  value: 50000,
  currency: 'KRW'
});

GTM 의 Custom Event Trigger이 dataLayer push 를 감지 → GA4 Tag 발사.

Workspace · Version · Environment

Workspace:
  - 협업 단위 (브랜치 같은 개념)
  - 여러 사람이 동시 변경 작업

Version:
  - Workspace 의 snapshot
  - "v15: 2026-05-17 caretive_change"

Environment:
  - Development · Staging · Production
  - 각 환경 다른 Version 배포

Git 같은 workflow — workspace = branch, version = commit, environment = deployment.

Preview · Publishing

Preview Mode:
  - 변경 사항 = 즉시 production 적용 X
  - "Tag Assistant Preview" 로 검증
  - 우리 사이트에 디버그 모드로 접속

Publishing:
  - Workspace → Version 생성
  - Version → Environment 배포
  - 변경 사항 production 활성

운영 안전선 — 프로덕션 직행 X, Preview 검증 후 Publish.

Server-side GTM (sGTM)

일반 GTM = 사용자 브라우저에서 vendor 서버로 직접 hit.

sGTM = 우리 서버 (Cloud Run · App Engine 등) 가 프록시 — 브라우저는 우리 서버로만 hit, 우리 서버가 각 vendor 로 분배.

장점은 네 갈래로 정리돼요. Cookie 통제 가 가능해 cookieless 환경에 대응할 수 있고, 우리 서버가 vendor 역할을 하니 ad blocker 회피 도 됩니다. 거기에 Server-side 데이터 보강 — PII (개인 식별 정보) 제거나 데이터 정제 — 가 한 박자 끼고, vendor 가 raw user data 를 못 보는 Privacy 통제 까지 따라옵니다.

대규모 운영 + privacy 가 중요한 환경 = sGTM 권장. 추가 비용 + 운영 부담 은 감수.

Firebase SDK — 모바일 앱

iOS (Swift)

import FirebaseCore
import FirebaseAnalytics

// AppDelegate
FirebaseApp.configure()

// Event 박기
Analytics.logEvent("product_viewed", parameters: [
    "product_id": "P-123",
    "category": "shoes",
    "price": 50000
])

// User Property
Analytics.setUserProperty("premium", forName: "user_tier")

// User ID
Analytics.setUserID("user-12345")

// Screen 추적
Analytics.logEvent(AnalyticsEventScreenView, parameters: [
    AnalyticsParameterScreenName: "ProductDetail",
    AnalyticsParameterScreenClass: "ProductDetailViewController"
])

Android (Kotlin)

import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase

val analytics = Firebase.analytics

// Event
analytics.logEvent("product_viewed") {
    param("product_id", "P-123")
    param("category", "shoes")
    param("price", 50000.0)
}

// User Property
analytics.setUserProperty("user_tier", "premium")

// User ID
analytics.setUserId("user-12345")

// Screen
analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {
    param(FirebaseAnalytics.Param.SCREEN_NAME, "ProductDetail")
}

Flutter / React Native

// Flutter
import 'package:firebase_analytics/firebase_analytics.dart';

FirebaseAnalytics analytics = FirebaseAnalytics.instance;

await analytics.logEvent(
  name: 'product_viewed',
  parameters: {
    'product_id': 'P-123',
    'category': 'shoes',
  },
);

자동 수집 Event (모바일)

Firebase SDK 가 자동 박는 event:

- first_open       (앱 첫 실행)
- session_start    (세션 시작)
- screen_view      (화면 전환)
- app_clear_data
- app_remove       (앱 삭제)
- app_update       (버전 update)
- in_app_purchase  (앱 내 결제 — 자동)
- os_update
- notification_*   (FCM 알림 관련)

설치만 해도 모바일 핵심 event 가 자동.

Crashlytics 결합

Firebase 의 Crashlytics (앱 크래시 수집 도구) 와 Analytics 가 자연 통합. 크래시 발생 → 자동 app_exception event 박힘 → GA4 에서 crash 사용자 segment 가능.

Remote Config — A/B Testing

Firebase Remote Config (서버에서 앱 설정값 원격 제어) 는 코드 배포 없이 설정값 변경 을 가능하게 합니다. Firebase A/B Testing 결합 = GA4 의 audience 기반 실험.

val remoteConfig = Firebase.remoteConfig
val buttonColor = remoteConfig.getString("button_color")  // "red" or "blue"

A/B 그룹 = GA4 audience 로 자동 매핑 + conversion 측정 자동.

Measurement Protocol — Server-side Hit

HTTP Endpoint

Measurement Protocol = HTTP requests 로 GA 서버 직접 event send. — 공식 docs

# Production endpoint
POST https://www.google-analytics.com/mp/collect
  ?measurement_id=G-XXXXXXXX
  &api_secret=YOUR_API_SECRET

# Validation server (debug)
POST https://www.google-analytics.com/debug/mp/collect
  ?measurement_id=G-XXXXXXXX
  &api_secret=YOUR_API_SECRET

Payload 구조

{
  "client_id": "555.123",
  "user_id": "user-12345",
  "events": [{
    "name": "purchase",
    "params": {
      "transaction_id": "T-12345",
      "value": 50000,
      "currency": "KRW",
      "items": [{
        "item_id": "P-1",
        "item_name": "Shoes",
        "price": 50000
      }]
    }
  }]
}

client_id 의 의미

Measurement Protocol 이 client_id 또는 app_instance_id기존 online 활동과 결합. — 공식 docs

핵심 — server-side hit 의 client_id브라우저의 GA cookie 의 client_id 와 일치 하면 → 같은 사용자로 통합.

// 브라우저에서 client_id 추출
function getClientId(callback) {
  gtag('get', 'G-XXXXXXXX', 'client_id', callback);
}

// 우리 백엔드로 보냄
getClientId((clientId) => {
  fetch('/api/server-event', {
    method: 'POST',
    body: JSON.stringify({ clientId: clientId, eventName: 'purchase', ... })
  });
});

// 백엔드 → Measurement Protocol
// 같은 clientId 로 GA send → 통합 추적

Validation Server

Validation server = production 보내기 전에 payload 검증.

curl -X POST 'https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXXX&api_secret=YOUR_SECRET' \
  -H 'Content-Type: application/json' \
  -d '{...}'

Response — validation 결과 (error · warning · 성공).

{
  "validationMessages": [
    {
      "fieldPath": "events[0].params.value",
      "description": "value must be numeric",
      "validationCode": "VALUE_INVALID"
    }
  ]
}

production endpoint 보내기 전 항상 validation. typo · type 오류를 사전 발견.

DebugView

GA4 console 의 DebugView = 실시간 event 흐름 확인:

Payload 에 debug_mode: 1 추가:
{
  "client_id": "...",
  "events": [...],
  "user_properties": {...},
  "debug_mode": true     // 또는 1
}

→ GA4 console 의 DebugView 에서 실시간 event 도착 확인. 디버그 환경에서만.

사용 Case

✓ 백엔드 결제 webhook → 서버에서 purchase event
✓ 환불 발생 → server-side refund event
✓ 구독 자동 결제 → 사용자 액션 없이 conversion
✓ 키오스크 · 시계 같은 client SDK 안 되는 환경
✓ CRM (고객 관계 관리) 시스템 event (이메일 open 등 — 서버에서 수집)
✓ 오프라인 conversion → 매장 결제 → online 사용자 매칭

한계

Measurement Protocol = gtag · Tag Manager · Firebase 의 enhance (보완). replace 아님. — 공식 docs

한계를 짚으면 네 가지예요. 일부 예약 event/parameter name 은 사용 X (자동 수집 전용) 이고, 일부 UI 의 custom event rule 은 MP 에 안 적용됩니다. Geo / device 정보 는 MP payload 에 명시 안 보내면 최근 client hit 의 정보 로 채워져요 (또는 session_id 결합). Real-time report 에는 조금 늦게 반영.

→ MP 만으로 완전한 추적 X. client SDK + MP 조합이 표준.

4 방법 결정 매트릭스

시나리오별

간단한 블로그/콘텐츠 사이트:
  → gtag.js 직접 (1 줄 추가)

복잡한 marketing stack:
  → GTM (web) + 여러 vendor Tag

모바일 앱:
  → Firebase SDK (자동 event + Crashlytics + Remote Config)

대규모 e-commerce:
  → GTM (web) + Firebase (app) + MP (server-side)

Privacy 중요 환경:
  → Server-side GTM (sGTM)

레거시 시스템:
  → Measurement Protocol (서버에서 직접)

결정 기준 — 5 질문

Q1: 어디서 측정? (Web · App · Server)
Q2: 누가 운영? (개발자 · 마케터 · 둘 다)
Q3: 다른 도구 (FB Pixel · Hotjar) 통합?
Q4: Privacy / Cookieless 요구?
Q5: Server-side event 필요?

대부분 큰 회사 = GTM (web) + Firebase (app) + Measurement Protocol (server)조합.

결정 매트릭스

상황 권장
신규 + 작은 사이트 gtag.js
마케터 자율 + 여러 도구 GTM
모바일 앱 Firebase SDK
Server-side event (webhook 등) Measurement Protocol
Privacy 강함 + 대규모 Server-side GTM
Hybrid (web + app + server) 3 조합

함정 정리

사고 1: SPA 의 page_view 누락

원인 — SPA 의 route change 가 자동 감지 X.

해결route hook 에서 수동 gtag('event', 'page_view', ...).

사고 2: GTM Preview 안 하고 Publish

원인Production 직행 → 잘못 설정한 Tag 가 모든 사용자에 영향.

해결Preview Mode 로 본인 brower 에서 검증 후 Publish.

사고 3: Cookie consent 와 GA 의 race condition

원인Cookie banner 표시gtag config순서가 잘못 → 동의 안 받은 상태에서 cookie 박힘.

해결gtag consent default = denied모든 다른 코드 앞 에. 동의 후 update.

사고 4: Firebase 와 GA4 Property 분리

원인 — 모바일 앱 = Firebase project, 웹 = 별도 GA4 property → 통합 X.

해결 — GA4 console 에서 Firebase project linking + 같은 property 의 다른 stream 으로 통합.

사고 5: Measurement Protocol 의 IP/Geo 누락

원인 — Server 가 hit 보낼 때 user IP 안 명시 → server IP 로 geo 추정 → 모든 사용자가 서버 위치 로 찍힘.

해결 — payload 에 user IP override 명시 (또는 client SDK 와 같은 session 결합).

사고 6: API Secret 노출

원인 — Measurement Protocol 의 api_secretClient SDK 또는 git public 에 박음.

해결Server only. 환경 변수 + secrets manager.

사고 7: gtag 의 dataLayer 충돌

원인GTM 과 gtag.js 동시 설치 → dataLayer 의 event hit 충돌 (중복 전송).

해결한 가지만 선택. GTM 권장 (그 안에 GA4 Tag 추가).

사고 8: Firebase debug build 의 추적

원인 — 개발 환경 (debug) 의 event 가 production property 로 박힘.

해결Firebase environment 별 property 분리 + debug build 의 GA collection 비활성.

사고 9: Server-side GTM 의 latency

원인 — sGTM 의 추가 hop (브라우저 → sGTM → GA) 으로 hit 지연.

해결 — sGTM 의 server location 을 사용자 근처 (Cloud Run regions) 로.

운영 권장 패턴

Pattern 1: 표준 Web 통합 — GTM

<!-- Head -->
<script>
  // 1. Consent Mode default (2편)
  gtag('consent', 'default', {
    ad_storage: 'denied',
    analytics_storage: 'denied'
  });
</script>

<!-- GTM container -->
<script>
  (function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX');
</script>
GTM Container 안 설정:
  Tag 1: GA4 Configuration (config tag)
    Measurement ID: G-XXXXXXXX
    Send Page View: Yes (또는 SPA 면 No)

  Tag 2: GA4 Event - Purchase
    Trigger: Custom Event "purchase" (dataLayer)

  Variable: DLV - transaction_id
    DataLayer Variable Name: ecommerce.transaction_id

Pattern 2: SPA 의 페이지 추적

// Next.js · React Router · Vue Router
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function GoogleAnalytics() {
  const location = useLocation();

  useEffect(() => {
    if (typeof window.gtag === 'function') {
      window.gtag('event', 'page_view', {
        page_location: window.location.href,
        page_title: document.title
      });
    }
  }, [location]);

  return null;
}

또는 GTM 의 History Change Trigger 활용.

Pattern 3: 모바일 통합

// Android Application 클래스
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        val analytics = Firebase.analytics

        // 환경별 enable/disable
        analytics.setAnalyticsCollectionEnabled(BuildConfig.DEBUG.not())

        // User ID (로그인 후)
        currentUser?.let {
            analytics.setUserId(it.id)
            analytics.setUserProperty("user_tier", it.tier)
        }
    }
}

Pattern 4: Server-side 결제 추적

# 결제 완료 webhook
@app.post('/webhooks/payment-completed')
async def on_payment_completed(payment: Payment):
    # 1. 결제 처리
    await process_payment(payment)

    # 2. GA4 Measurement Protocol
    payload = {
        "client_id": payment.client_id,  # 브라우저 client_id (앞서 저장)
        "user_id": payment.user_id,
        "events": [{
            "name": "purchase",
            "params": {
                "transaction_id": payment.id,
                "value": payment.amount,
                "currency": "KRW",
                "items": [
                    {
                        "item_id": item.product_id,
                        "item_name": item.name,
                        "price": item.price,
                        "quantity": item.quantity
                    }
                    for item in payment.items
                ]
            }
        }]
    }

    async with httpx.AsyncClient() as client:
        await client.post(
            "https://www.google-analytics.com/mp/collect",
            params={
                "measurement_id": GA_MEASUREMENT_ID,
                "api_secret": GA_API_SECRET  # env var
            },
            json=payload
        )

Pattern 5: Validation 워크플로

# 새 event 박을 때 — 항상 validation 먼저
async def validate_event(payload):
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "https://www.google-analytics.com/debug/mp/collect",
            params={
                "measurement_id": GA_MEASUREMENT_ID,
                "api_secret": GA_API_SECRET
            },
            json=payload
        )
        data = resp.json()
        if data.get("validationMessages"):
            raise ValueError(f"GA validation failed: {data['validationMessages']}")
        return True

# 사용
async def send_event(payload):
    if NODE_ENV != "production":
        await validate_event(payload)

    await actual_send(payload)

개발 환경에서 항상 validation, production 직행 X.

Pattern 6: client_id 추출 + Server 결합

// 브라우저 → 서버 (한 번)
async function syncClientIdToServer() {
  return new Promise((resolve) => {
    gtag('get', 'G-XXXXXXXX', 'client_id', (clientId) => {
      // localStorage 저장 (서버 API 호출 시 사용)
      sessionStorage.setItem('ga_client_id', clientId);

      // 또는 우리 서버로 명시 sync
      fetch('/api/user/ga-client-id', {
        method: 'POST',
        body: JSON.stringify({ clientId })
      });

      resolve(clientId);
    });
  });
}

// 로그인 후 한 번 호출
syncClientIdToServer();

서버가 Measurement Protocol 을 쓸 때 같은 client_id 를 보내면 = 통합 추적.

시험 직전 한 번 더 — 측정 4 방법 함정 압축 노트

gtag.js

  • dataLayer 의 wrapper · 내부적으로 push
  • Command 4종 — config · event · set · consent
  • config 옵션 — send_page_view · cookie_* · anonymize_ip · allow_google_signals
  • SPA 함정 — 자동 page_view 1회만, route hook 에서 수동 박기

GTM (Google Tag Manager)

  • Container · Tag · Trigger · Variable · DataLayer
  • Workspace · Version · Environment (Git 같은 workflow)
  • Preview · Publishing (Production 직행 X)
  • Tag 종류 풍부 (GA4 · FB Pixel · Hotjar · Custom)
  • Trigger — Page View · Click · Form · Scroll · Custom Event · Timer
  • Variable — Built-in (Page URL · Click Text) + User-defined (DLV · JS · Lookup · Custom)
  • Server-side GTM (sGTM) — 우리 서버가 프록시 (cookieless · privacy 강화 · 추가 비용)

Firebase SDK (모바일)

  • iOS · Android · Flutter · React Native
  • 자동 event — first_open · session_start · screen_view · in_app_purchase · ...
  • Crashlytics 결합 (app_exception)
  • Remote Config + A/B Testing
  • GA4 property 와 linking 필요 (별도 X)

Measurement Protocol (Server-side)

  • HTTP POST /mp/collect (production) · /debug/mp/collect (validation)
  • client_id 결합 — 브라우저의 GA cookie 와 일치 시 같은 사용자
  • Validation server = production 보내기 전 검증 (typo · type 오류)
  • DebugView = 실시간 event 확인 (debug_mode: 1)
  • 사용 case — 결제 webhook · 환불 · 키오스크 · 오프라인 conversion · CRM
  • gtag · GTM · Firebase 의 enhance · replace X
  • API Secret = server only

결정 매트릭스

  • 작은 사이트 → gtag.js
  • 복잡한 marketing → GTM
  • 모바일 → Firebase
  • Server event → MP
  • Privacy 강함 → sGTM
  • 큰 회사 = GTM + Firebase + MP 조합

사고

  • SPA page_view 누락 (route hook)
  • GTM Preview 안 하고 Publish
  • Consent 와 GA race condition
  • Firebase 와 GA4 분리 (linking 필요)
  • MP 의 IP/Geo 누락
  • API Secret 노출 (server only)
  • gtag + GTM 동시 (중복 hit)
  • Firebase debug build 의 production 박힘
  • sGTM latency (server location)

패턴

  • 표준 Web GTM 통합 (Consent + Container + Tag)
  • SPA route hook page_view
  • 모바일 Firebase + 환경 분리
  • Server-side 결제 추적 (MP + client_id 결합)
  • Validation 워크플로 (production 전 항상)
  • client_id 추출 + Server sync (cross-device)

공식 문서: Measurement Protocol GA4 · Google Tag Manager 에서 원문을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!