스택이 다른 사이트 셋에 같은 추적 코드 심기
한 도메인 아래 사이트 넷을 굴리는데 스택이 제각각이다.
ttakochan.com Astro 정적 (MPA)
longshort.ttakochan.com Next.js 16 App Router
counter.ttakochan.com Vite + React SPA
map.ttakochan.com Next.js
여기에 Google 태그 관리자를 컨테이너 하나로 심었다. 스니펫은 똑같은데 넣는 자리와 실패 방식이 스택마다 달랐다.
만들 것
각 사이트에서 컨테이너가 로드되고 dataLayer 에 초기화 이벤트가 한 번만 쌓이는 상태.
전제
- 컨테이너 ID 하나 (
GTM-XXXXXXX) - 각 저장소의 최상위 레이아웃 파일을 고칠 수 있을 것
- 하위 도메인끼리 같은 상위 도메인 아래에 있을 것
마지막 조건이 중요하다. 같은 도메인 아래면 쿠키가 자동으로 공유돼서 교차 도메인 설정이 필요 없다.
Astro 정적
src/layouts/Base.astro 처럼 모든 페이지를 감싸는 레이아웃 하나에만 넣으면 된다.
<head>
<meta charset="utf-8" />
<script is:inline>
(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','GTM-XXXXXXX');
</script>
</head>
is:inline 이 없으면 안 된다. Astro 는 인라인 스크립트를 기본으로 번들에 넣고 별도 .js 파일로 빼낸다. 그러면 <head> 최상단 실행 보장이 깨진다.
확인
빌드 결과에 스크립트가 인라인으로 남아 있어야 한다.
grep -o 'src="[^"]*gtm[^"]*\.js"' dist/index.html
여기서 뭔가 잡혔다면 is:inline 을 빠뜨려 외부 파일로 빠진 거다.
Next.js App Router
App Router 에서는 레이아웃에 <head> 를 직접 못 쓴다. <body> 첫머리에 넣는다.
const GTM_ID = "GTM-XXXXXXX";
const gtmInit = `(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','${GTM_ID}');`;
export default function Layout({ children }) {
return (
<html lang="ko">
<body>
<script dangerouslySetInnerHTML={{ __html: gtmInit }} />
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
height="0" width="0"
style={{ display: "none", visibility: "hidden" }}
/>
</noscript>
{children}
</body>
</html>
);
}
페인트 전에 돌아야 하는 스크립트가 이미 있다면
내 경우 테마 깜빡임을 막는 스크립트가 <body> 첫 줄에 있었다. 그건 GTM 보다 앞에 둔다. GTM 은 몇십 밀리초 늦어도 상관없지만 테마는 늦으면 화면이 번쩍인다.
확인
빌드 산출물에서 세는데 여기서 한 번 헷갈린다.
grep -o 'GTM-XXXXXXX' .next/server/app/ko/index.html | wc -l
Next.js 는 HTML 과 RSC 페이로드 양쪽에 마크업을 넣는다. 그래서 2배로 나온다. 실제로 실행되는 건 한 번이다. RSC 페이로드는 데이터지 실행되는 HTML 이 아니다.
DOM 에서 확인해야 정확하다.
[...document.scripts].filter(s => s.src.includes('gtm.js')).length // 1 이어야 정상
(window.dataLayer||[]).filter(o => o && o['gtm.start']).length // 1 이어야 정상
404 페이지는 빠진다
_not-found 와 _global-error 는 [locale] 레이아웃 밖이라 스크립트가 안 들어간다. 내 경우 111개 페이지 중 109개만 적용됐다. 404 유입을 추적하려면 루트 레이아웃을 따로 만들어야 한다.
Vite + React SPA
index.html 하나가 전부라 제일 단순하다. 원본 스니펫을 고칠 게 없다.
<head> 끝에 스크립트를, <body> 첫 줄에 noscript iframe 을 넣는다. 빌드하면 dist/index.html 에 그대로 들어간다. 번들러가 건드리지 않는다.
CSP 가 있으면 헤더도 열어야 한다
이게 제일 오래 걸렸다. 스니펫은 HTML 에 멀쩡히 들어 있고 스크립트 태그도 DOM 에 있는데 아무 일도 안 일어났다.
performance.getEntriesByType('resource')
.filter(e => e.name.includes('googletagmanager'))
// [{ transferSize: 0, duration: 0 }]
요청은 나갔는데 아무것도 못 받았다. 콘솔에 답이 있었다.
Loading the script 'https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX'
violates the following Content Security Policy directive: "script-src ..."
CSP 를 쓰는 사이트는 헤더를 같이 열어야 한다. 지시어가 넷이고 하나씩 열면 다음 게 막힌다. 이건 분량이 있어서 따로 쓴다.
마지막 확인
세 사이트에서 같은 걸 찍어 값이 전부 같아야 한다.
JSON.stringify({
container: Object.keys(window.google_tag_manager||{}).includes('GTM-XXXXXXX'),
gtm_start: (window.dataLayer||[]).filter(o=>o&&o['gtm.start']).length,
scripts: [...document.scripts].filter(s=>s.src.includes('gtm.js')).length,
noscript: [...document.querySelectorAll('noscript')].filter(n=>n.innerHTML.includes('ns.html')).length,
})
container: true, gtm_start: 1, scripts: 1, noscript: 1
콘솔 오류가 0 건이어야 한다. 탭을 새로 열고 확인해라. 같은 탭에서 여러 번 새로고침하면 이전 로드의 오류 로그가 남아 있어서 판단이 안 된다. 나는 이것 때문에 고친 뒤에도 안 고쳐진 줄 알았다.
안 되면
컨테이너는 뜨는데 아무 태그도 안 돈다. 정상이다. 컨테이너는 빈 상자라 안에 태그를 만들어 게시해야 돈다.
게시했는데도 안 돈다. 컨테이너 스크립트를 직접 받아 확인한다.
curl -s "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX" | grep -c 'G-XXXXXXXXXX'
0 이면 게시가 안 된 거다. 미리보기에서 잘 보이는 것과 게시는 별개다.
한 사이트만 안 된다. 그 사이트에 CSP 가 있는지 본다.
하지 말 것
애드센스를 태그 관리자로 넣지 마라. Google 이 권장하지 않는다. 비동기 로드라 광고 표시가 늦어진다. 광고 스크립트는 코드에 직접 두는 게 맞다.
컨테이너를 여러 사이트가 공유하면 트리거에 호스트 조건을 걸어라. 안 걸면 한 사이트용 이벤트 태그가 네 곳에서 다 발동한다.
Page Hostname equals map.ttakochan.com
트리거가 스무 개쯤 됐을 때는 되돌리기 어렵다. 처음부터 규칙으로 잡는 편이 낫다.