Ao trabalhar com código legado, é comum ver funcionalidades a “quebrarem” depois de atualizar uma biblioteca ou framework. Mas, na maioria das vezes, o que realmente está quebrado não é a ferramenta, é o nosso entendimento de como ela funcionava.
Foi exatamente isso que aconteceu quando migrámos um projeto antigo em Next.js para o App Router. O Google Tag Manager não deixou de funcionar. Sempre foi frágil. Estava a funcionar por acaso, não por arquitetura.
A armadilha da migração: quando o analytics funcionava antes — e o que mudou
A aplicação já estava construída em Next.js, mas não utilizava o next/link. Por isso, cada navegação causava um reload completo da página. Como resultado, o evento nativo page_view do GTM era disparado corretamente em cada mudança de rota.
Do ponto de vista de analytics, tudo parecia sólido.
Com a introdução do App Router e do next/link, a aplicação passou a ter todos os benefícios esperados: navegação client-side, layouts persistentes, scripts persistentes e prefetching. Mas houve um efeito secundário inesperado. Como a mudança de rota deixou de ser equivalente a um carregamento de página, o evento page_view passou a ser disparado apenas no carregamento inicial.
À primeira vista, isto pareceu uma regressão, como se a migração tivesse introduzido um bug no analytics.
Na realidade, a migração apenas expôs o quão frágil o setup já era. Nunca tinha sido pensado para navegação client-side. Apenas funcionava antes por coincidência.
A mudança de mentalidade: pageviews já não são páginas
É aqui que muitos developers têm dificuldade, especialmente durante migrações.
Quando uma aplicação usa links nativos ou força reloads completos, um page_view é sempre disparado. O browser recarrega, o GTM reinicializa e o analytics funciona como esperado.
Com o App Router e o next/link, a navegação passa a ser uma transição de estado, não um carregamento de página. A aplicação controla o routing, e o analytics precisa de reagir a essas mudanças de estado, não a “páginas” no sentido tradicional.
Ou seja, o GTM deixa de observar carregamentos de página. Passa a ter de observar mudanças de rota.
O insight prático: observar o único sinal fiável
Quando este modelo mental fica claro, a pergunta torna-se simples:
Qual é o único sinal fiável de que ocorreu uma navegação?
A resposta é o pathname.
Uma mudança no pathname é o único evento inequívoco de navegação numa aplicação com routing client-side. A partir daí, a decisão arquitetural torna-se clara:
Cada mudança de rota é um pageview.
Para implementar isto em código, podemos emitir um evento personalizado sempre que o pathname muda. Para manter a responsabilidade centralizada e evitar lógica duplicada, o ideal é fazer isto dentro de um provider:
const GTMProvider = ({ children }: { children: React.ReactNode }) => {
const pathname = usePathname();
useEffect(() => {
window.dataLayer.push({
event: 'pageview',
page_path: pathname,
});
}, [pathname]);
return children;
};
export default GTMProvider;
Do lado do GTM, a correção é igualmente importante e muitas vezes ignorada:
todas as tags que antes dependiam do trigger nativo page_view devem ser atualizadas para ouvir este novo evento personalizado.
Sem esta alteração, a implementação parece correta, mas nada é disparado.
Conclusão: porque isto importa para além de “corrigir o analytics”
Uma solução de analytics robusta não é apenas uma questão técnica ou uma preferência arquitetural. É um problema de produto, marketing e conversão.
Num mercado dominado por SPAs, confiar a aquisição e medição de milhares de utilizadores a um sistema que funciona por coincidência é uma decisão arriscada. O App Router e o next/link não quebraram o analytics, tornaram visíveis suposições escondidas e expuseram uma arquitetura fraca.
Quando compreendemos verdadeiramente as ferramentas com que trabalhamos, a solução torna-se não só mais simples, mas também fiável.