Tracking de SPA: History Change, virtual pageviews e armadilhas

\"Tracking

SPA sem page_view virtual confiável vira analytics cego: rotas mudam, o GTM não dispara, e mídia otimiza um funil que não existe. History Change bem desenhado — com dataLayer estável, dedupe e título/URL corretos — reconstitui a navegação. O erro mais caro é disparar page_view demais ou de menos e chamar isso de “implementação moderna”. Em stacks React/Vue/Angular e Next/Nuxt, o contrato de rota é tão crítico quanto a taxonomia de conversão.

History change e page_view virtual
History change e page_view virtual

Por que pageview clássico falha em React, Vue e Angular?

Em MPA, cada navegação recarrega o documento e a tag dispara de novo. Em SPA, o roteador troca a view via History API (pushState/replaceState) ou hash sem reload. O container GTM carregado uma vez não “vê” page_view automático do GA4 a cada rota — a menos que você instrumente. O resultado operacional é um site que “parece” ter analytics, mas só registra a primeira pintura.

Sintomas: uma única page_view por sessão, engajamento distorcido, funis impossíveis, e Enhanced Measurement capturando scroll/clique em contexto de URL errada. Times mid-market descobrem isso quando migram o site institucional para Next/Nuxt e os relatórios “quebram do dia para a noite”. O volume de page_views despenca, o tempo engajado sobe de forma estranha e as landings do Ads passam a concentrar tudo na home.

Há um segundo efeito colateral: eventos de conversão continuam disparando, mas com page_location da rota anterior. O Google Ads “otimiza” leads que na verdade ocorreram no step de pricing, enquanto o relatório de landing aponta outra URL. Sem page_view virtual alinhado ao momento da conversão, CRO e mídia discutem páginas fantasma.

History Change no GTM resolve tudo?

O trigger History Change escuta mudanças de histórico e é o atalho mais comum. Não é suficiente sozinho: você precisa definir o que conta como “página”, evitar double-firing com o page_view inicial, e garantir que page_location, page_title e page_referrer (virtual) estejam corretos no momento do hit. History Change sem filtro vira metrônomo de replaceState.

Em apps com replaceState agressivo (auth redirects, query params de filtro), History Change pode disparar em mudanças que não são “páginas” de negócio. Filtre por fragmento de path, ignore query-only changes quando fizer sentido, e documente a regra. Um listing com facetas que reescreve a query a cada checkbox não deve gerar vinte page_views — deve gerar eventos de filtro e, no máximo, um page_view quando o path canônico muda.

Trate History Change como camada de escuta, não como definição de produto. A definição de “página” pertence ao mapa de rotas do negócio: o que o usuário percebe como troca de contexto e o que o dashboard precisa contar como etapa de funil.

  • Disparar page_view na carga inicial + em mudanças de rota relevantes
  • Dedupe: não mandar dois page_views no first load
  • Atualizar title e location antes do hit
  • Ignorar ruído de query/hash quando não for página
  • Manter taxonomia de eventos de negócio independente da rota

dataLayer push vs só History Change: qual padrão usar?

O padrão mais robusto em produto maduro é o roteador emitir um evento explícito (ex.: virtualPageview / page_view) no dataLayer com path, title e metadados de template. O GTM só escuta esse contrato. History Change fica como fallback ou para sites sem acesso fácil ao código. Contrato explícito transforma “analytics de SPA” em API interna versionável.

Contrato explícito reduz falso positivo de replaceState e facilita QA. Exija do front: um push por navegação de negócio, sem duplicar em Strict Mode de desenvolvimento sem controle. Inclua page_type, content_group e, quando fizer sentido, template_name — para o GA4 não depender de regex frágil em path.

Em migrações, o híbrido funciona: History Change cobre rotas legadas enquanto o app novo emite dataLayer. O ponto de saída do híbrido deve estar datado; híbrido eterno vira duas fontes de verdade e double-count intermitente.

AbordagemPrósContrasQuando preferir
History Change (GTM)Rápido, sem deploy de appRuído de replaceState; title atrasadoMarketing site SPA simples
dataLayer no routerContrato claro; metadados ricosExige engajamento do frontProduto / app com rotas complexas
gtag no routerDireto ao GA4Bypass de GTM; governança piorEvitar se GTM é fonte da verdade
Hybrid (router + filtro GTM)ResiliênciaMais peças para documentarMigração gradual

Como montar o page_view virtual sem duplicar?

Fluxo típico: na Configuration tag, desative o page_view automático se você for controlar 100% via eventos; ou mantenha o automático só no first load e use History Change com exceção de fragmento inicial. O importante é uma regra explícita — não “os dois e torcer”. Documente a regra no próprio workspace do GTM e no README de tagueamento.

No evento page_view, envie page_location com URL canônica (origem + path), page_title atualizado (document.title depois do router), e parâmetros de conteúdo (content_group, page_type) se a taxonomia existir. Sem title correto, relatórios de landing page viram lista de “Home” eterna. Se o title for i18n ou vier de CMS assíncrono, dispare o hit após o title commit — ou envie um parâmetro page_title_override com o valor de negócio.

Canonicalização evita fragmentar a mesma rota: trailing slash, locale prefix e default query params devem seguir a mesma regra do SEO. Analytics que diverge do canônico de SEO cria dois “mundos” de landing page e atrapalha comparação pré/pós redesign.

Strict Mode, hydration e double mount

Em React 18 Strict Mode (dev), efeitos podem montar duas vezes e duplicar pushes. Não “corrija” só em produção ignorando o problema: proteja com idempotência (último path enviado) ou dispare a partir de um único ponto do router history listener. QA sempre em build de preview próximo de produção. Hydration mismatch que remonta o provider de analytics também duplica hits — trate o listener de rota como singleton da aplicação, não como efeito de cada page component.

Eventos de negócio em SPA: o que não misturar com page_view?

Modais, drawers e tabs não são páginas — a menos que a URL mude e o negócio trate como tal. Se tudo vira page_view, o engajamento explode e o funil perde significado. Use eventos (login_modal_open, filter_apply) para UI; reserve page_view para rotas. A regra prática: se o botão “voltar” do browser não muda o contexto da mesma forma, provavelmente não é página.

Para mídia, converta só eventos de outcome (generate_lead, purchase). Page_view virtual alimenta análise de caminho; não deve ser a conversão do Google Ads por engano. Em funis de checkout SPA, cada step com URL própria pode ser page_view; step sem URL deve ser evento de funil — mistura dos dois modelos no mesmo release é a receita para exploração ilegível.

Mantenha um glossário curto no time: página, microinteração, conversão. Sem glossário, cada squad inventa um page_view para “dar visibilidade” e o GA4 vira dump de UI.

Como validar em staging e produção?

Navegue o mapa de rotas críticas: home → listing → detalhe → checkout. No GTM Preview e GA4 DebugView, confirme um page_view por rota, parâmetros corretos, e eventos de conversão com page_location da etapa certa. Compare volume diário de page_views vs sessões antes/depois do release — saltos de 2x ou quedas bruscas pedem rollback de regra.

Automatize smoke tests de dataLayer no CI quando possível (playwright checando pushes). SPA sem regressão de tagueamento é dívida técnica disfarçada de front moderno. Inclua asserts de “não houve segundo page_view no first load” e “path X gera page_type Y”.

Em produção, monitore razão page_views/sessão por device e por template. Mudança abrupta após deploy de router é alerta de instrumentação, não necessariamente de conteúdo. Acople isso ao mesmo canal de alerta de tags que vocês já usam para quedas de purchase.

Governança: quem “dona” a rota no analytics?

Defina um doc curto: lista de rotas = páginas; o que é evento; quem altera o router avisa mensuração. Sem isso, cada feature flag cria um buraco. A DataScroll costuma amarrar taxonomia de page_type/content_group ao design system de rotas — para o dashboard não depender do humor do path.

Inclua no definition of done de front: push de dataLayer (ou confirmação de History Change filtrado) + evidência de Preview. Rota nova sem analytics é bug de produto, não “melhoria futura”. O owner do contrato pode ser marketing ops + um tech lead de front — o importante é existir um nome responsável quando o volume quebrar.

Checklist prático

  • Decisão documentada: History Change, dataLayer no router, ou híbrido
  • Regra anti-duplicação no first load
  • page_location e page_title corretos a cada rota
  • Filtro de replaceState/query-only quando aplicável
  • Eventos de UI separados de page_view
  • QA GTM Preview + DebugView no mapa de rotas
  • Comparação de volume page_view/sessão pós-release
  • Owner de contrato dataLayer no time de produto

Erros comuns

  • Confiar só no Enhanced Measurement sem virtual pageview
  • Disparar page_view em todo replaceState de filtro
  • Enviar page_view duplicado no load (auto + History Change)
  • Title antigo no hit porque o router atualiza depois
  • Tratar modal como página e inflar engajamento
  • Mudar de MPA para SPA sem revisitar conversões do Google Ads
  • Ignorar diferenças entre ambiente dev (Strict Mode) e produção

SPA com pageview fantasma ou sumido?

A DataScroll instrumenta SPAs com eventos estáveis e QA de rota para analytics e mídia enxergarem a navegação real.

Instrumentar minha SPA Falar com a DataScroll

Perguntas frequentes

Devo desligar o page_view automático do GA4?

Se você controla 100% via eventos de rota, desligar o automático evita duplicata. Se mantiver automático, History Change precisa excluir o first load. Escolha um modelo e teste — não combine os dois no escuro. Em migração, aceite um curto período de medição paralela em staging antes de promover.

Hash router (#/rota) precisa de setup diferente?

History Change pode capturar hash changes, mas valide. Prefira pushes explícitos do router com path canônico. Relatórios ficam mais limpos com URLs previsíveis, e integrações de Ads/SEO sofrem menos com fragmentos opacos.

Next.js App Router muda alguma coisa?

Sim: navegação client-side e RSC mudam timing de title/URL. Instrumente no ponto em que a rota “commitou” para o usuário, não só no click. Teste soft navigation com Preview e confirme que soft nav não herda page_title da rota anterior.

page_referrer em SPA fica errado — e agora?

Muitos setups deixam o referrer do documento original. Para análise de caminho interno, use eventos com previous_path no dataLayer. Não espere que referrer HTTP se comporte como em MPA — modele o caminho com parâmetro próprio se o funil depender disso.

Posso usar só screen_view como no app?

Na web GA4, o padrão analítico de marketing continua sendo page_view. screen_view faz sentido em streams app. Não misture conceitos sem necessidade — unifique taxonomia de “tela/rota” no modelo de dados se App+Web for o caso, preferencialmente no warehouse.

Como evitar page_view em query de UTM?

Se o router reescreve só a query, filtre no trigger (comparar path anterior vs atual). UTMs não devem gerar página nova a cada parâmetro de campanha. Normalize também gclid/fbclid para não fragmentar path analytics.

GTM Server-Side exige algo especial para SPA?

O browser ainda precisa gerar o evento certo; o sGTM só encaminha. Lixo in → lixo out. A qualidade do page_view virtual continua sendo problema de front/GTM web — server-side não conserta rota sem hit.

Qual entrega a DataScroll recomenda?

Contrato dataLayer no router + tags GTM versionadas + QA de rotas críticas. History Change puro só quando não há acesso a código e o site é simples. Em produto com checkout SPA, contrato explícito quase sempre paga o custo de alinhamento com o front.