서드파티 SDK 본체가 아직 로드되지 않았을 때, 작은 스텁(stub)이 들어온 호출을 배열에 쌓아 두고 본체가 도착하면 쌓인 호출을 순서대로 처리하는 방식. Google Tag Manager(GTM)의 dataLayer와 gtag.js의 gtag()가 이 구조이고, Meta Pixel의 fbq() 설치 코드도 본체 전 호출을 배열에 쌓는다. 쌓아 두는 배열은 먼저 들어온 것부터 꺼내는 큐로 쓰인다.
GTM 설치 스니펫
<script>
window.dataLayer = window.dataLayer || [];
</script>
<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','GTM-XXXXXX');</script>- 스니펫이 하는 일은 셋이다.
dataLayer배열을 만들고(w[l]=w[l]||[]), 시작 메시지(gtm.start,event: 'gtm.js')를 넣고,gtm.js를async로 삽입한다 - 본체가 오기 전
dataLayer는 그냥 배열이다.dataLayer.push()는 배열에 항목을 붙일 뿐이다 - 컨테이너가 로드되면 GTM은 쌓여 있던 메시지를 처리하기 시작한다. 들어온 순서대로(FIFO, First-In First-Out) 한 번에 하나씩 처리하고, 메시지가 이벤트면 조건이 맞은 태그를 실행한 뒤 다음 메시지로 넘어간다
- 본체가 로드된 뒤의
dataLayer.push()·gtag()호출도 큐에 들어가 앞서 대기 중인 메시지 다음에 처리된다 || []덕분에 앱 코드와 스니펫 중 어느 쪽이 먼저 실행돼도 같은 배열을 쓴다. 반대로dataLayer = [...]처럼 다시 대입하면 앞서 쌓인 값이 사라지므로 공식 문서도 덮어쓰지 말라고 한다
gtag와 fbq
gtag.js도 같은 dataLayer를 큐로 쓴다. gtag()는 받은 인자를 그대로 넣는 한 줄짜리 함수다.
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'TAG_ID');Meta Pixel 기본 코드는 분기를 스텁 안에 둔다. fbq를 부르면 callMethod가 있을 때는 그쪽으로 넘기고, 없으면 fbq.queue에 인자를 쌓는다. 스니펫은 fbq.queue = []로 큐를 만들고 fbevents.js를 async로 삽입한다. callMethod를 정의하는 코드는 스니펫에 없다.
n = f.fbq = function () {
n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments)
}로드를 늦춰도 되는 이유
스텁은 몇 줄짜리라 일찍 실행해도 부담이 적다. 무거운 본체는 하이드레이션 뒤나 브라우저가 한가할 때로 미뤄도, 그사이 호출은 큐에 남았다가 나중에 처리된다. 그래서 스텁을 먼저 두면 본체 로드를 늦춰 첫 화면·하이드레이션과의 메인 스레드 경합(롱 태스크)을 줄일 수 있다(INP(Interaction to Next Paint), TBT(Total Blocking Time)).
Next.js next/script(2026 기준)의 strategy로 예를 들면 다음과 같다.
afterInteractive(기본값): 하이드레이션이 일부 또는 전부 끝난 뒤 로드한다. 공식 문서가 태그 매니저·분석 도구의 후보로 든다lazyOnload: 페이지의 리소스를 다 받은 뒤 브라우저 유휴 시간에 로드한다. 채팅 위젯처럼 급하지 않은 스크립트용이다
한계
- 큐는 페이지 메모리에만 있다. 공식 문서도
dataLayer변수는 방문자가 그 페이지에 머무는 동안만 유지된다고 한다. 본체가 오기 전에 사용자가 떠나면 쌓인 이벤트는 보내지지 않고 사라진다. 본체를 늦출수록 이 구간이 길어진다 - 스텁은 명령을 받아 적기만 한다. 반환값으로 무언가를 읽어 오는 API는 스텁 단계에서 답할 수 없다. gtag.js에서 값을 읽는
gtag('get', target, field_name, callback)도 결과를 반환값이 아니라 콜백으로 준다 - 모든 SDK에 스텁이 있지는 않다. 스텁 없이 전역 객체를 바로 부르는 SDK는 로드를 늦추면 호출이 에러를 내거나 유실된다. 늦추기 전에 그 SDK의 설치 코드가 본체 전 호출을 큐에 쌓는지 확인한다
이탈 직전에 꼭 남겨야 하는 데이터라면 navigator.sendBeacon()을 검토할 수 있다. MDN은 분석 데이터를 페이지 이탈이나 다음 이동을 늦추지 않고 보내는 용도로 이 메서드를 소개하고, unload 대신 visibilitychange에서 보내라고 권한다(unload는 bfcache(뒤로·앞으로 가기 캐시)와도 맞지 않는다). 아직 보내지 않은 데이터를 합쳐 약 64KiB까지라, 그보다 크면 fetch()를 검토한다.
출처: Google Tag Manager — The data layer: Installation · How data layer information is processed · Persist data layer variables · Troubleshooting · Google tag — gtag.js API reference: get · Meta Pixel — Get Started: Base Code · Next.js — Script Component: strategy · MDN — Navigator: sendBeacon()