React SPA에서 GTM 설치하기: 자동 페이지뷰 확인부터 중복 점검까지
React SPA의 GTM 설치와 페이지뷰 수집은 별개입니다. 자동 측정을 먼저 확인하고 필요한 경우에만 수동 이벤트를 연결하는 순서와 Next.js 설치, 중복 점검을 설명합니다.

세 줄로 먼저 읽기
- React SPA는 화면을 바꿀 때 문서를 새로 받지 않아 GTM의 기본 페이지뷰 트리거가 다시 실행되지 않습니다.
- GA4 향상된 측정은 브라우저 기록 변경을 감지해 page_view를 보낼 수 있으므로 자동 수집이 정상이면 수동 이벤트를 더하지 않습니다.
- 수동 수집으로 바꿀 때는 sendpageview를 false로 두고 향상된 측정의 기록 변경 수집도 꺼야 한 번 이동이 두 번 쌓이지 않습니다.
React로 만든 사이트에서 메뉴를 눌렀는데 GA4 페이지뷰가 늘지 않는다면, GTM을 다시 설치하기 전에 컨테이너 설치와 화면 이동 수집을 나누어 봐야 합니다. 설치가 정상이어도 화면 이동을 측정하는 설정이 틀릴 수 있고, 자동 수집이 이미 동작하는데 수동 이벤트를 더하면 같은 이동이 두 번 기록됩니다.
GTM(Google Tag Manager)은 측정 태그를 한곳에서 관리하는 도구입니다. 컨테이너는 태그 설정을 묶은 단위이고 GTM-으로 시작하는 ID로 구분합니다. GA4는 사이트 이용 현황을 분석하는 서비스이며, 웹 데이터를 받는 데이터 스트림에는 G-으로 시작하는 측정 ID가 붙습니다. 두 ID는 들어가는 곳이 다르니 서로 바꾸어 넣지 않습니다.
코드를 고칠 권한이 없는 마케터는 아래에서 정리할 기대 횟수를 개발 담당자에게 먼저 넘기고, 설정이 끝나면 같은 기준으로 결과를 대조합니다.
SPA는 무엇이 다른가요?
SPA(Single Page Application)는 문서 하나를 불러온 뒤 필요한 화면만 바꿔 끼우는 웹앱 방식입니다. React는 이런 화면을 만드는 라이브러리이고, React로 만든 사이트라도 모든 이동이 SPA 방식인 것은 아닙니다.
일반적인 페이지 이동에서는 브라우저가 새 HTML을 받고 GTM도 처음부터 다시 실행됩니다. SPA 내부 이동에서는 라우팅(주소에 맞는 화면을 골라 보여 주는 처리)이 브라우저 안에서 화면만 바꾸므로 문서를 새로 받지 않습니다. 그래서 GTM의 기본 페이지뷰 트리거가 이동할 때마다 다시 실행되지 않습니다.
페이지 이동 방식에 따라 실행되는 측정 장치
| 일어나는 일 | ||
|---|---|---|
| 브라우저가 새 문서를 받음 | 제공 | 제공하지 않음 |
| GTM 기본 페이지뷰 트리거 실행 | 제공 | 제공하지 않음 |
| GTM History Change 트리거 실행 | – | 제공 |
| GA4 향상된 측정의 page_view | 제공 | 기록 변경 감지를 켰을 때 |
SPA 이동을 잡아낼 방법은 두 가지입니다. GA4의 향상된 측정은 설정에 따라 브라우저 방문 기록의 변경을 감지해 페이지뷰 이벤트인 page_view를 보냅니다. GTM에는 같은 변경에 반응하는 History Change 트리거가 있습니다. 둘 중 하나만 켜는 것이 중복을 막는 첫 조건입니다.
먼저 두 화면을 오가며 기대 횟수를 적습니다
시험할 사이트와 그 사이트의 GTM 작업 공간, GA4 웹 스트림을 준비하고, 지금 켜져 있는 설치 방식과 수집 설정을 적어 둡니다. 그다음 GTM 미리보기로 접속해 화면을 오가며 페이지뷰가 몇 번 들어오는지 봅니다. 예를 들어 /about과 /products 두 화면이 있는 사이트라면 기대 횟수는 다음과 같습니다.
두 화면을 오갈 때 기대하는 페이지뷰
1회/about에 처음 접속그 화면의 주소와 제목
+1회메뉴로 /products 이동새 주소와 제목, 이전 페이지
+1회뒤로 가기로 /about 복귀돌아온 주소와 제목
0회이동 없이 다시 렌더링같은 화면을 또 기록하면 중복
렌더링은 화면 내용을 계산해 다시 그리는 과정입니다. 화면이 다시 그려졌다고 새 페이지 조회로 집계하지는 않습니다. 탭 전환이나 검색 조건 변경을 새 조회로 볼지는 서비스마다 다르므로 시험 전에 정해 둡니다.
1. GTM 컨테이너는 한 번만 설치합니다
사이트 코드나 관리 플러그인이 같은 컨테이너를 이미 넣고 있지 않은지부터 봅니다. 설치 방식을 둘 이상 함께 쓰면 태그가 두 번 실행됩니다.
| 프로젝트 상황 | 설치 방법 |
|---|---|
| 일반 React 앱에서 HTML 진입 파일을 직접 관리 | GTM 관리 화면의 스니펫을 정해진 위치에 붙여 넣기 |
| 기존 GTM 연동 패키지를 이미 씀 | 그 패키지의 현재 버전 문서에서 초기화 위치를 찾아 한 곳으로 정리 |
| Next.js App Router | 공식 GoogleTagManager 컴포넌트 사용(아래 예시) |
스니펫을 직접 붙여 넣는 경우의 순서는 다음과 같습니다.
GTM 스니펫을 붙여 넣는 순서
1. 스니펫 가져오기GTM 관리 메뉴의
Google 태그 관리자 설치
2. 스크립트 넣기head 태그 안
가능한 한 위쪽
3. noscript 넣기여는 body 태그
바로 뒤
4. 미리보기로 확인Tag Assistant에
컨테이너 ID가 보이면 성공
화면이 바뀔 때마다 컨테이너를 다시 넣는 코드는 쓰지 않습니다. SPA에서는 처음 불러온 컨테이너가 이후 이동에도 그대로 남아 있습니다.
Next.js에서 설치하는 예시
Next.js는 React 기반 프레임워크입니다. App Router의 app/layout.tsx에는 여러 화면에 공통으로 들어가는 구조를 둡니다. 아래 예시는 @next/third-parties 패키지가 이미 설치된 프로젝트를 기준으로 합니다. 패키지가 없다면 개발 담당자가 프로젝트의 Next.js 버전과 맞는 패키지 버전인지 본 뒤 추가합니다. 이 예시를 쓰려고 프로젝트 전체를 최신 버전으로 올리지는 않습니다.
// app/layout.tsx
import type { ReactNode } from 'react';
import { GoogleTagManager } from '@next/third-parties/google';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="ko">
<GoogleTagManager gtmId="GTM-XXXXXXX" />
<body>{children}</body>
</html>
);
}GTM-XXXXXXX를 실제 컨테이너 ID로 바꿉니다. 기존 레이아웃에 있던 폰트, 메타데이터, 공통 컴포넌트는 그대로 두고 GoogleTagManager 줄만 합칩니다. 공식 문서는 이 패키지를 실험적 라이브러리로 안내하므로, 프로젝트에 설치된 버전의 문서를 기준으로 씁니다. GTM 안에서 이미 GA4 태그를 설정했다면 GoogleAnalytics 컴포넌트를 함께 넣지 않습니다. 함께 넣으면 같은 페이지뷰가 두 경로로 나갑니다.
2. 자동 페이지뷰 설정부터 봅니다
수동 코드를 쓰기 전에 GA4 설정 화면부터 엽니다. GA4가 화면 이동을 이미 수집하고 있을 수 있습니다. 향상된 측정은 대상 측정 ID로 Google 태그가 설정되어 있어야 동작합니다.
GA4에서 기록 변경 수집 설정을 찾는 순서
1. 관리데이터 수집 및 수정
데이터 스트림
2. 웹 스트림 열기사이트의
측정 ID 확인
3. 향상된 측정톱니 버튼을 눌러
페이지 조회 항목 펼치기
4. 기록 변경 항목브라우저 기록 이벤트 기반
페이지 변경이 켜져 있는지
이 설정이 켜진 상태에서 앞의 두 화면 시험을 했을 때 첫 진입과 이동이 한 번씩 들어오고 주소와 제목도 맞으면 수동 코드는 더하지 않습니다. 주소가 바뀌지 않는 앱이나 제목이 화면보다 늦게 정해지는 앱은 자동 측정만으로 맞는 값이 나오지 않을 수 있어, 미리보기에서 실제로 전달된 주소와 제목을 봅니다.
GTM의 Page View 트리거가 다시 실행되지 않아도 GA4의 자동 페이지뷰는 들어올 수 있습니다. 두 기능을 같은 것으로 보면 멀쩡한 설정을 고치게 됩니다. 한편 Google이 안내하는 GTM용 SPA 구성처럼 기록 변경을 GTM 태그로 처리한다면 GA4의 자동 기록 변경 수집은 끕니다. 자동 수집과 수동 태그 중 하나만 남깁니다.
3. 수동 수집이 필요한 경우에만 설정합니다
수동 방식에서는 앱이 '측정할 화면이 준비됐다'는 신호를 보내고 GTM이 그 신호를 받아 GA4로 전달합니다. 브라우저 기록 변경에 반응하는 방법도 있지만, 여기서는 제목과 주소가 준비된 시점을 앱이 직접 알려 주는 방식을 예로 듭니다.
먼저 자동 수집을 끕니다
모든 페이지뷰를 수동으로 보내기로 했다면 첫 로딩과 이후 이동을 모두 수동 이벤트가 맡습니다. 자동 수집이 남아 있으면 중복이 생기므로 아래 순서로 끕니다.
수동 수집으로 바꾸기 전에 끌 것
1. 첫 자동 페이지뷰Google 태그 설정에서
send_page_view를 false로
2. 기록 변경 수집향상된 측정의
기록 기반 페이지 변경 끄기
3. 기존 태그 찾기같은 이벤트를 보내는
History Change 태그 정리
4. 첫 진입 시험수동 이벤트가
한 번씩만 들어오면 성공
send_page_view에는 불리언(참과 거짓 두 가지만 담는 값) false를 넣습니다. 따옴표로 감싼 문자열 "false"는 다른 값으로 처리됩니다. 이 값은 첫 로딩의 자동 페이지뷰만 막고, 기록 변경 기반 수집은 2단계에서 따로 꺼야 합니다. Google 태그 자체를 지우거나 All Pages 트리거에 연결된 다른 태그를 한꺼번에 지우지는 않습니다. 동의 처리와 기본 태그 설정도 그대로 둡니다.
앱에서 준비된 화면 정보를 보냅니다
아래는 개발 담당자가 화면 준비 시점에 부를 함수의 예시입니다. 이 함수는 라우팅을 스스로 감지하지 않으므로, 측정할 화면이 준비될 때마다 직접 호출해야 합니다.
// analytics.ts: call in the browser after a measured screen is ready.
export function notifyScreenReady(
pageLocation: string,
pageTitle: string,
pageReferrer: string
) {
if (typeof window === 'undefined') return;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'virtual_page_view',
page_location: pageLocation,
page_title: pageTitle,
page_referrer: pageReferrer,
});
}
declare global {
interface Window {
dataLayer?: Record<string, unknown>[];
}
}/about에서 /products로 이동한 뒤라면 다음처럼 부릅니다.
notifyScreenReady(
'https://example.com/products',
'상품 목록',
'https://example.com/about'
);호출하면 dataLayer에 virtual_page_view 객체가 하나 추가됩니다. 이 함수에는 중복을 막는 장치가 없어서 두 번 부르면 두 개가 쌓입니다. 첫 진입과 측정할 화면마다 한 번씩만 부르도록 연결합니다. 세 가지 값에는 각각 다음 내용을 넣습니다.
첫 진입과 내부 이동에서 넣는 값
| 전달값 | ||
|---|---|---|
| page_location | 들어온 화면의 전체 주소 | 새 화면의 전체 주소 |
| page_title | 준비된 화면 제목 | 새로 준비된 제목 |
| page_referrer | 실제 유입 참조 그대로 보존 | 직전에 측정한 화면 주소 |
프로젝트에 이미 Window.dataLayer 타입 선언이 있다면 새로 선언하지 않고 기존 타입에 맞춥니다.
호출 시점은 신경 써서 정합니다. useEffect는 화면을 그린 뒤 외부 시스템과 연결할 때 쓰는 React 기능인데, useEffect 안에서 document.title을 읽는다고 새 제목이 준비됐다는 보장은 없습니다. Next.js에서 현재 경로를 읽는 usePathname만 지켜보면 주소의 물음표 뒤 검색 매개변수만 바뀌는 이동을 놓칩니다. 반대로 검색 매개변수가 바뀔 때마다 보내면 조회 수가 지나치게 늘어납니다. 무엇을 화면 조회로 볼지와 앱이 언제 준비되는지를 먼저 합의합니다.
GTM에서 신호와 GA4 태그를 연결합니다
dataLayer는 앱이 GTM에 신호와 값을 넘기는 데이터 구조입니다. 위 코드의 virtual_page_view는 GTM이 받는 맞춤 신호 이름이고, GA4로 보내는 이벤트 이름 page_view와 일부러 다르게 지었습니다. 같은 값을 앱, GTM, GA4에서 각각 어떤 이름으로 다루는지 정리하면 다음과 같습니다.
앱에서 GA4까지 단계별로 쓰는 이름
| 값 | |||
|---|---|---|---|
| 신호와 이벤트 | virtual_page_view | 맞춤 이벤트 트리거 | page_view |
| 화면 주소 | page_location | DLV - page_location | page_location |
| 화면 제목 | page_title | DLV - page_title | page_title |
GTM에서는 다음 순서로 만듭니다.
- 변수 → 새로 만들기 → 데이터 영역 변수에서
page_location,page_title,page_referrer를 각각 읽는 변수를 만듭니다. 이름은DLV - page_location처럼 구분하기 쉽게 붙입니다. - 트리거 → 새로 만들기 → 맞춤 이벤트에서 이벤트 이름을
virtual_page_view로 지정합니다. - Google 애널리틱스: GA4 이벤트 태그를 만들고 대상 측정 ID와 이벤트 이름
page_view를 넣습니다. - 태그의 이벤트 매개변수 세 개에 1번의 데이터 영역 변수를 연결하고, 2번의 맞춤 이벤트 트리거를 고릅니다.
- 기본 Google 태그와 동의 설정이 이 태그보다 먼저 준비되는지 본 뒤 미리보기에서 시험합니다.
page_path 값만 추가해서는 전체 주소인 page_location이 바뀌지 않으므로, 이 예시는 page_location에 전체 주소를 직접 넣습니다.
4. 설치 확인과 수집 확인을 나눠서 봅니다
Tag Assistant는 GTM 미리보기에서 태그가 실행됐는지와 어떤 값이 전달됐는지 보여 주는 점검 도구입니다. DebugView는 GA4에서 디버그 모드로 들어온 이벤트를 실시간으로 보여 주는 화면입니다. 두 화면을 아래 순서로 봅니다.
미리보기에서 GA4까지 확인하는 순서
1. 이벤트 고르기Tag Assistant 왼쪽에서
virtual_page_view 선택
2. 변수 값 보기주소와 제목이
지금 화면과 같은지
3. 태그 실행GA4 이벤트 태그가
Fired로 표시되는지
4. DebugView 대조GA4 관리의 데이터 표시
같은 기기의 page_view
DebugView에 이벤트가 보이려면 디버그 모드와 동의 상태 같은 수집 조건이 맞아야 합니다. 화면별 확인 방법은 GTM 디버깅 가이드에 더 자세히 적었습니다. 결과가 기대와 다르면 증상별로 다음을 먼저 봅니다.
| 증상 | 먼저 볼 곳 |
|---|---|
| 첫 진입만 들어옴 | 자동 기록 변경 수집이 꺼져 있거나 수동 신호가 연결되지 않음 |
| 한 번 이동에 두 번 들어옴 | 자동과 수동이 함께 켜져 있거나 컨테이너, 태그가 두 번 설치됨 |
| 주소가 이전 화면 것임 | 전송 시점이 이르거나 page_location 변수가 틀림 |
| 제목이 이전 화면 것임 | 새 제목이 준비되기 전에 보냄 |
| 개발 환경에서만 두 번 들어옴 | StrictMode가 Effect를 한 번 더 실행함 |
React StrictMode는 개발 중에 Effect를 한 번 더 실행해 문제를 드러내는 기능입니다. 중복을 감추려고 StrictMode부터 끄지 말고, 수집 코드가 언제 실행되는지 보고 운영용 빌드에서도 시험합니다. 한 번 보낸 주소를 다시 보내지 않도록 막아 버리면 뒤로 가기 같은 정상 재방문도 빠집니다.
태그가 Fired로 표시되는 것만으로는 부족합니다. 처음 접속, 내부 이동, 뒤로 가기에서 기대한 횟수와 값이 들어오고, 이동 없는 재렌더링에서는 새 이벤트가 없어야 설정이 끝난 것입니다. 미리보기에서 통과한 설정은 게시한 뒤 실제 사이트에서도 같은 조건으로 한 번 더 봅니다.
자동 측정으로 주소와 제목까지 맞게 들어오던 사이트에 수동 virtual_page_view를 붙인 뒤 한 번 이동에 두 번씩 들어오기 시작했다면 GTM을 다시 설치하지 말고, 추가한 수동 설정을 되돌린 뒤 두 화면 시험을 다시 합니다.
Sources
- Google 애널리틱스: 단일 페이지 애플리케이션 측정
- Google 애널리틱스: 페이지뷰 수집 설정
- Google 태그 관리자: 웹 설치
- Next.js: 외부 라이브러리
- React: StrictMode
3줄 요약
-
React SPA는 화면을 바꿀 때 문서를 새로 받지 않아 GTM의 기본 페이지뷰 트리거가 다시 실행되지 않습니다.
-
GA4 향상된 측정은 브라우저 기록 변경을 감지해 page_view를 보낼 수 있으므로 자동 수집이 정상이면 수동 이벤트를 더하지 않습니다.
-
수동 수집으로 바꿀 때는 send_page_view를 false로 두고 향상된 측정의 기록 변경 수집도 꺼야 한 번 이동이 두 번 쌓이지 않습니다.

제대로 이해했는지 한 문제로 확인해 볼까요?
답을 고르면 바로 풀이가 나와요.
화면을 한 번 이동할 때 page_view가 이미 한 번 정확히 수집됩니다. 먼저 할 일은 무엇인가요?

새 글과 AI 소식을 메일로 받아 보세요
AI가 바꾸는 일과 도구, 측정 실무 이야기를 매주 한 번 보내 드려요.
이 글이 도움이 되었나요?
메일, 회의록, 리포트처럼 반복되는 일을 AI로 자동화하는 순서와 안전장치를 다룹니다
코스 전체 보기 →