Agendando Eventos dataLayer para Otimizar o INP

Como o adiamento de eventos do GTM para depois da pintura do navegador melhora as suas pontuações de INP

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

Resumo: Melhorando o INP Adiando o dataLayer.push()

Quando o dataLayer.push() é acionado de forma síncrona dentro de um manipulador de eventos (event handler), o GTM processa as tags antes que o navegador possa pintar (paint) o feedback visual. Isso bloqueia a thread principal (main thread) e infla o seu Interaction to Next Paint (INP). A solução: agende o dataLayer.push() para rodar depois que o navegador tiver pintado o próximo quadro (frame) usando o requestAnimationFrame. Este padrão corta de 20 a 100 ms do INP na maioria dos sites, frequentemente transformando uma pontuação reprovada em uma aprovada. A coleta de dados é atrasada em 50 a 250 ms, o que é irrelevante para o rastreamento (tracking) de analytics e marketing.

Como o dataLayer.push() prejudica o seu INP

Para um dos nossos clientes, medimos uma redução de 100 ms no Interaction to Next Paint (INP) reagendando quando o dataLayer.push() é executado após a interação de um usuário. A correção é uma substituição simples em JavaScript que prioriza a renderização em vez do rastreamento.

Revisado pela última vez por Arjen Karel em março de 2026

O problema com o dataLayer.push() síncrono

Se você trabalha com o Google Tag Manager, você conhece o dataLayer.push(). É o método padrão para enviar eventos para a Camada de Dados (Data Layer), e está profundamente integrado na maioria das funcionalidades dos sites. O problema é que ninguém questiona quando ele é executado.

Quando o dataLayer.push() é chamado diretamente dentro de um manipulador de eventos (um clique de botão, o envio de um formulário, a alternância de um menu), ele roda de forma síncrona. As tags do GTM configuradas para disparar nesse evento também são executadas imediatamente, bloqueando a thread principal antes que o navegador possa atualizar o layout. O visitante clica em um botão e não vê nada acontecer enquanto o GTM processa scripts de rastreamento em segundo plano. Esse atraso é o seu atraso de apresentação (presentation delay), e é o principal contribuinte para um INP ruim.

O rastro (trace) de performance abaixo, de um grande site de notícias, mostra a atividade do GTM após uma interação do usuário. As tarefas do GTM levaram aproximadamente 90 ms para serem executadas, elevando o INP geral para 263 ms. Essa interação reprova no Core Web Vitals.

A correção: pinte primeiro, dê push depois

A solução está alinhada com as orientações de otimização do INP da web.dev: ceda (yield) para a thread principal para que o navegador possa pintar antes de executar códigos não críticos. Em vez de executar o rastreamento de forma síncrona dentro do manipulador de eventos:

  1. Execute o código para a atualização visual imediatamente.
  2. Deixe o navegador pintar (paint).
  3. Em seguida, faça o push para o dataLayer.

Aqui está a mesma interação depois da aplicação deste padrão. A única mudança é que o dataLayer.push() agora roda depois que o navegador pintou o próximo quadro.

A interação que antes falhava agora passa com folga. Todos os dados ainda chegam ao dataLayer. A diferença é que os scripts do GTM são executados depois que o navegador responde à ação do usuário, não antes.

O ajudante awaitPaint

Esta função auxiliar (helper) usa requestAnimationFrame para agendar um callback depois que o navegador tiver pintado. O setTimeout aninhado garante que a função rode depois que a pintura for concluída, e não logo antes dela. Este é o padrão recomendado pela web.dev para adiar trabalhos não críticos após uma interação.

async function awaitPaint(fn) {
    await new Promise((resolve) => {
        // Fallback: garante que não travemos se o RAF nunca disparar (ex: abas em segundo plano)
        setTimeout(resolve, 200);

        // Agenda para depois da próxima pintura
        requestAnimationFrame(() => {
            setTimeout(resolve, 50);
        });
    });

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

Usando o ajudante

Envolva qualquer chamada dataLayer.push() em awaitPaint para adiá-la até depois da pintura:

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

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

// Uso
document.querySelector('.buy-button').addEventListener('click', () => {
    // O feedback visual acontece imediatamente
    showLoadingSpinner();

    // O rastreamento dispara depois da pintura
    pushToDataLayer('purchase_click', { productId: '123' });
});

Sobrescrita global para testes rápidos

Para testar este padrão em todo o seu site sem ter que refatorar toda chamada dataLayer.push(), você pode sobrescrever (override) globalmente a função push. Coloque este script no <head> imediatamente após o snippet de contêiner do GTM.

<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>

Isso sobrescreve todos os dataLayer.push() na página. Todos os gatilhos de eventos do GTM existentes, chamadas de rastreamento codificadas diretamente (hardcoded) e integrações de plugins se beneficiam automaticamente, sem nenhuma alteração no código.

Por que isso melhora o INP

O INP mede o tempo desde uma interação do usuário até que o navegador pinte a resposta visual. Se você executar o processamento de eventos do GTM de forma síncrona dentro de um manipulador de eventos, você bloqueia a thread principal e impede a renderização. O Web Almanac de 2024 descobriu que o atraso de apresentação é o maior contribuinte para um INP ruim na mediana, e os scripts de rastreamento estão entre as causas principais.

Os números confirmam isso. O Web Almanac de 2025 mostra que 77% das páginas mobile passam no INP em geral, mas apenas 63% dos 1.000 sites mais visitados passam. Esses sites do topo carregam mais scripts de terceiros. Na Subito (o maior marketplace de classificados da Itália), desativar um único script de rastreamento do TikTok carregado via GTM derrubou o INP de 208 ms para aproximadamente 170 ms. Um script, 38 ms economizados.

Ao adiar o dataLayer.push() para depois da pintura, você tira todo o processamento do GTM do caminho crítico da interação. O usuário obtém feedback visual imediatamente. O GTM ainda é disparado, só que 50 a 250 ms depois.

Por que não um atraso fixo, Idle Callback ou scheduler.yield()?

  • Atraso fixo com setTimeout(delay): Um atraso codificado diretamente (ex: setTimeout(..., 100)) é tentar adivinhar quando a renderização será concluída. Muito longo e você atrasa o rastreamento desnecessariamente. Muito curto e você ainda bloqueia a pintura.
  • requestIdleCallback: Agenda o trabalho para quando o navegador estiver ocioso (idle), mas não garante a execução logo após uma interação específica. O callback pode rodar muito mais tarde, ou nem rodar antes de o usuário sair da página.
  • scheduler.yield(): A API moderna para ceder o controle à thread principal. Ela preserva a prioridade da tarefa (diferente do setTimeout, que envia a sua continuação para o final da fila). No entanto, o scheduler.yield() não garante que uma pintura tenha ocorrido antes da execução do seu código. Para este caso de uso específico, o requestAnimationFrame é a melhor ferramenta porque está vinculado ao ciclo de vida da renderização. Observe que o scheduler.yield() ainda não é suportado no Safari.

A combinação requestAnimationFrame + setTimeout é especificamente projetada para este cenário. O requestAnimationFrame é disparado logo antes do navegador pintar. O setTimeout aninhado cria uma nova tarefa que roda depois que a pintura é concluída. Juntos, eles garantem que o seu código adiado seja executado após a atualização visual, que é exatamente o que o INP mede.

Compensações (Trade-offs)

  • Micro-atraso na coleta de dados: Os eventos chegam ao GTM 50 a 250 ms mais tarde do que com o push síncrono. Para o rastreamento de analytics e marketing, isso é irrelevante.
  • Perda de dados em saída rápida: Se um visitante acionar um evento e sair da página dentro da janela de atraso, esse evento pode não ser disparado. Para eventos críticos antes de um redirecionamento ou descarregamento (unload) da página, use o navigator.sendBeacon() em vez do dataLayer.push(). Veja os argumentos para limitar scripts de analytics para saber mais sobre a API Beacon.

Para a maior parte dos rastreamentos, o ganho de INP supera de longe o atraso mínimo. Se você precisar de precisão de milissegundos no registro de eventos (bidding em tempo real, painéis financeiros), essa abordagem não é adequada. Para todo o resto, é uma vitória clara.

Depois de aplicar a sobrescrita, verifique a melhoria com o Real User Monitoring. Testes de laboratório mostrarão a diferença, mas são os dados de campo (field data) de usuários reais em redes reais que contam para as suas pontuações no 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.

Search Console flagged your site?

When Google flags your Core Web Vitals you need a clear diagnosis fast. I deliver a prioritized fix list within 48 hours.

Request Urgent Audit
Agendando Eventos dataLayer para Otimizar o INPCore Web Vitals Agendando Eventos dataLayer para Otimizar o INP