INP 최적화를 위한 dataLayer 이벤트 스케줄링

브라우저 페인트 이후로 GTM 이벤트를 지연시켜 INP 점수를 향상시키는 방법

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-05

TL;DR: dataLayer.push() 지연을 통한 INP 개선

이벤트 핸들러 내에서 dataLayer.push()가 동기적으로 실행되면 브라우저가 시각적 피드백을 페인트하기 전에 GTM이 태그를 처리합니다. 이는 메인 스레드를 차단하고 Interaction to Next Paint (INP)를 증가시킵니다. 해결책: requestAnimationFrame을 사용하여 브라우저가 다음 프레임을 페인트한 후 dataLayer.push()가 실행되도록 스케줄링하세요. 이 패턴은 대부분의 사이트에서 INP를 20~100ms 단축하여 종종 실패 점수를 통과 점수로 바꿉니다. 데이터 수집은 50~250ms 지연되지만, 이는 분석 및 마케팅 트래킹에는 무의미한 수준입니다.

dataLayer.push()가 INP에 악영향을 미치는 이유

저희 고객사 중 한 곳에서 사용자 상호작용 후 dataLayer.push()가 실행되는 시점을 재조정하여 Interaction to Next Paint (INP)를 100ms 단축했습니다. 이 해결책은 트래킹보다 렌더링을 우선시하는 JavaScript 드롭인 대체제입니다.

최종 검토자: Arjen Karel (2026년 3월)

동기식 dataLayer.push()의 문제점

Google Tag Manager로 작업해본 적이 있다면 dataLayer.push()에 대해 알고 계실 것입니다. 이는 이벤트를 Data Layer로 전송하는 표준 방법이며 대부분의 사이트 기능에 깊게 통합되어 있습니다. 문제는 이것이 언제 실행되는지 아무도 의문을 제기하지 않는다는 것입니다.

dataLayer.push()가 이벤트 핸들러(버튼 클릭, 폼 제출, 메뉴 토글 등) 내에서 직접 호출되면 동기적으로 실행됩니다. 해당 이벤트에서 발생하도록 구성된 GTM 태그도 즉시 실행되어 브라우저가 레이아웃을 업데이트하기 전에 메인 스레드를 차단합니다. 방문자가 버튼을 클릭해도 GTM이 백그라운드에서 트래킹 스크립트를 처리하는 동안 아무런 변화를 볼 수 없습니다. 이 지연이 바로 presentation delay이며 불량한 INP의 주요 원인입니다.

주요 뉴스 웹사이트에서 가져온 아래 성능 추적은 사용자 상호작용 후 GTM 활동을 보여줍니다. GTM 작업 실행에 약 90ms가 소요되어 전체 INP가 263ms로 증가했습니다. 이 상호작용은 Core Web Vitals 테스트를 통과하지 못합니다.

해결책: 먼저 페인트한 다음 푸시

이 솔루션은 web.dev의 INP 최적화 가이드라인과 일치합니다. 브라우저가 비핵심 코드를 실행하기 전에 페인트할 수 있도록 메인 스레드에 yield하세요. 이벤트 핸들러 내에서 트래킹을 동기적으로 실행하는 대신 다음과 같이 하세요:

  1. 시각적 업데이트를 위한 코드를 즉시 실행합니다.
  2. 브라우저가 페인트하도록 합니다.
  3. 그런 다음 dataLayer로 푸시합니다.

이 패턴을 적용한 후의 동일한 상호작용은 다음과 같습니다. 유일한 변경 사항은 이제 브라우저가 다음 프레임을 페인트한 후에 dataLayer.push()가 실행된다는 것입니다.

awaitPaint 헬퍼

이 헬퍼는 브라우저가 페인트한 후 콜백을 스케줄링하기 위해 requestAnimationFrame을 사용합니다. 중첩된 setTimeout은 페인트 직전이 아니라 페인트가 완료된 후에 함수가 실행되도록 보장합니다. 이는 상호작용 후 비핵심 작업을 지연시키기 위해 web.dev에서 권장하는 패턴입니다.

async function awaitPaint(fn) {
    await new Promise((resolve) => {
        // Fallback: ensures we don't hang if RAF never fires (e.g. background tabs)
        setTimeout(resolve, 200);

        // Schedule after the next paint
        requestAnimationFrame(() => {
            setTimeout(resolve, 50);
        });
    });

    if (typeof fn === 'function') {
        fn();
    }
}

헬퍼 사용하기

페인트 이후까지 지연시키려면 dataLayer.push() 호출을 awaitPaint로 래핑하세요:

function pushToDataLayer(event, data) {
    window.dataLayer = window.dataLayer || [];

    awaitPaint(() => {
        window.dataLayer.push({
            event,
            ...data,
            timestamp: new Date().toISOString()
        });
    });
}

// Usage
document.querySelector('.buy-button').addEventListener('click', () => {
    // Visual feedback happens immediately
    showLoadingSpinner();

    // Tracking fires after paint
    pushToDataLayer('purchase_click', { productId: '123' });
});

빠른 테스트를 위한 전역 오버라이드

모든 dataLayer.push() 호출을 리팩토링하지 않고 전체 사이트에서 이 패턴을 테스트하려면 push 함수를 전역으로 오버라이드할 수 있습니다. GTM 컨테이너 스니펫 직후 <head>에 이 스크립트를 배치하세요.

<script type="module">
    window.dataLayer = window.dataLayer || [];

    async function awaitPaint(fn) {
        return new Promise((resolve) => {
            const fallbackTimeout = setTimeout(() => {
                if (typeof fn === 'function') { fn(); }
                resolve();
            }, 200);
            requestAnimationFrame(() => {
                setTimeout(() => {
                    clearTimeout(fallbackTimeout);
                    if (typeof fn === 'function') { fn(); }
                    resolve();
                }, 50);
            });
        });
    }

    if (window.dataLayer && typeof window.dataLayer.push === 'function') {
        const originalPush = window.dataLayer.push.bind(window.dataLayer);

        window.dataLayer.push = function (...args) {
            (async () => {
                await awaitPaint(() => {
                    originalPush(...args);
                });
            })();
        };
    }
</script>

이는 페이지의 모든 dataLayer.push()를 오버라이드합니다. 기존의 모든 GTM 이벤트 트리거, 하드코딩된 트래킹 호출 및 플러그인 통합은 코드 변경 없이 자동으로 이점을 얻습니다.

이것이 INP를 개선하는 이유

INP는 사용자 상호작용부터 브라우저가 시각적 응답을 페인트할 때까지의 시간을 측정합니다. 이벤트 핸들러 내에서 GTM 이벤트 처리를 동기적으로 실행하면 메인 스레드를 차단하고 렌더링을 방해합니다. 2024년 Web Almanac에 따르면 중간값 기준으로 presentation delay가 불량한 INP의 가장 큰 원인이며 트래킹 스크립트가 주요 원인 중 하나입니다.

수치가 이를 증명합니다. 2025년 Web Almanac에 따르면 모바일 페이지의 77%가 전반적으로 INP를 통과하지만, 방문자가 가장 많은 상위 1,000개 사이트 중에서는 63%만이 통과합니다. 상위 사이트일수록 서드파티 스크립트를 더 많이 로드합니다. Subito(이탈리아 최대의 광고 포털)에서는 GTM을 통해 로드되는 단일 TikTok 트래킹 스크립트를 비활성화하여 INP를 208ms에서 약 170ms로 단축했습니다. 스크립트 하나로 38ms를 절약한 것입니다.

페인트 이후까지 dataLayer.push()를 지연시킴으로써 모든 GTM 처리를 상호작용의 크리티컬 패스에서 벗어나게 합니다. 사용자는 즉각적인 시각적 피드백을 얻습니다. GTM은 여전히 실행되지만, 50~250ms 더 늦게 실행될 뿐입니다.

고정된 지연, Idle Callback 또는 scheduler.yield()를 사용하지 않는 이유는 무엇인가요?

  • 고정된 setTimeout(delay): 하드코딩된 지연(예: setTimeout(..., 100))은 렌더링이 언제 완료될지 추측하는 것입니다. 너무 길면 트래킹이 불필요하게 지연됩니다. 너무 짧으면 여전히 페인트를 차단합니다.
  • requestIdleCallback: 브라우저가 유휴 상태일 때 작업을 스케줄링하지만 특정 상호작용 직후에 신속하게 실행된다고 보장하지는 않습니다. 콜백이 훨씬 늦게 실행되거나 사용자가 다른 페이지로 이동하기 전까지 아예 실행되지 않을 수도 있습니다.
  • scheduler.yield(): 메인 스레드에 yielding하기 위한 최신 API입니다. 작업 우선순위를 유지합니다(연속된 작업을 대기열의 맨 뒤로 보내는 setTimeout과 다름). 하지만 scheduler.yield()는 코드가 실행되기 전에 페인트가 발생했음을 보장하지 않습니다. 이 특정 사용 사례의 경우 렌더링 라이프사이클과 연결되어 있으므로 requestAnimationFrame이 더 나은 도구입니다. scheduler.yield()는 아직 Safari에서 지원되지 않습니다.

requestAnimationFrame + setTimeout 조합은 이 시나리오를 위해 특별히 설계되었습니다. requestAnimationFrame은 브라우저가 페인트하기 직전에 실행됩니다. 중첩된 setTimeout은 페인트가 완료된 후 실행되는 새로운 작업을 생성합니다. 이 둘이 함께 결합되어 지연된 코드가 시각적 업데이트 후에 실행되도록 보장하며, 이것이 바로 INP가 측정하는 것입니다.

트레이드오프

  • 데이터 수집의 미세한 지연: 이벤트가 동기식 푸시보다 50~250ms 늦게 GTM에 도달합니다. 분석 및 마케팅 트래킹의 경우 이는 무의미한 수준입니다.
  • 빠른 이탈 시 데이터 손실: 방문자가 이벤트를 트리거하고 지연 시간 내에 페이지를 이탈하면 해당 이벤트가 실행되지 않을 수 있습니다. 리디렉션 또는 페이지 언로드 전의 중요한 이벤트의 경우 dataLayer.push() 대신 navigator.sendBeacon()을 사용하세요. Beacon API에 대한 자세한 내용은 분석 스크립트 제한 사례를 참조하세요.

대부분의 트래킹에서 INP 개선 효과는 최소한의 지연을 훨씬 능가합니다. 이벤트 로깅에 밀리초 단위의 정밀도가 필요한 경우(실시간 입찰, 금융 대시보드) 이 접근 방식은 적합하지 않습니다. 그 외의 모든 경우에는 확실한 이점을 제공합니다.

오버라이드를 적용한 후 Real User Monitoring을 통해 개선 사항을 확인하세요. 랩 테스트에서도 차이를 확인할 수 있지만 Core Web Vitals 점수에 중요한 것은 실제 네트워크 환경의 실제 사용자가 생성한 필드 데이터입니다.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

사이트를 Core Web Vitals 통과까지.

유럽 주요 퍼블리셔와 이커머스 플랫폼 50만 페이지 이상. 수정은 제가 직접 짜고, 필드 데이터로 검증합니다.

진행 방식
INP 최적화를 위한 dataLayer 이벤트 스케줄링Core Web Vitals INP 최적화를 위한 dataLayer 이벤트 스케줄링