Projetos construídos em Next.js carregam uma sofisticação técnica que impressiona em performance e SEO - mas essa mesma arquitetura cria um ponto cego crítico para analytics: a perda silenciosa de dados de conversão. Diferente de sites tradicionais onde cada navegação aciona um carregamento completo de página, o Next.js opera com roteamento client-side que preserva o estado da aplicação. O resultado? Pageviews que simplesmente não são registrados no Google Analytics 4, eventos de conversão que nunca chegam ao funil, e decisões de negócio tomadas com base em dados incompletos. Estudos de implementação mostram que projetos sem rastreamento adaptado perdem entre 20% e 40% dos eventos de navegação - um buraco negro de informação que distorce completamente a análise de jornada do usuário.
O problema não é trivial, e a solução padrão do Google Tag Manager não resolve sozinha. A natureza híbrida do Next.js - combinando Server-Side Rendering (SSR), Static Site Generation (SSG) e Client-Side Rendering (CSR) - exige uma abordagem cirúrgica que respeite o ciclo de vida dos componentes React e os diferentes modos de renderização. Este guia técnico detalha exatamente como implementar GA4 em Next.js de forma que cada mudança de rota, cada interação em componente e cada evento de conversão seja capturado corretamente, sem duplicação e sem perda de dados. A diferença entre o App Router e o Pages Router torna essa implementação ainda mais nuançada - e é justamente essa diferença que separa uma integração funcional de uma implementação de fato confiável.
Por que implementar GA4 em Next.js é diferente de um site tradicional
A arquitetura do Next.js rompe com o paradigma clássico de navegação web onde cada clique em um link provoca um request HTTP completo, um novo carregamento de HTML e a reinicialização do JavaScript da página. Nesse modelo tradicional, o snippet do Google Analytics é executado a cada pageview porque o navegador literalmente reconstrói o DOM inteiro. No Next.js, porém, a navegação acontece via client-side navigation: o framework intercepta os cliques em links, busca apenas os dados necessários e atualiza o conteúdo dinamicamente via JavaScript - sem recarregar a página. Isso preserva o estado da aplicação, mantém animações fluidas e entrega performance excepcional - mas o script do GA4 não é re-executado, porque do ponto de vista técnico não houve um novo carregamento de página.
Esse comportamento transforma o rastreamento em um desafio de timing e integração com o ciclo de vida do React. O script gtag.js instalado tradicionalmente no <head> dispara corretamente na primeira visualização (quando há de fato um carregamento inicial), mas fica inerte nas navegações subsequentes. O desenvolvedor precisa instrumentar manualmente cada mudança de rota, disparando eventos pageview via JavaScript sempre que o componente de rota mudar. Sem essa instrumentação, o GA4 registra apenas a landing page - todas as outras navegações internas ficam invisíveis, colapsando a análise de funil e impossibilitando a compreensão real do comportamento do usuário dentro da aplicação.
O problema do roteamento client-side e a perda de pageviews
O roteamento client-side é mediado pelo componente next/link e pelo hook useRouter, que manipulam o histórico do navegador (History API) sem provocar requisições HTTP completas. Quando um usuário clica em um link interno, o Next.js carrega via AJAX o JSON da nova página (se for SSG) ou faz uma chamada à API de dados (se for SSR), atualiza o componente da rota e altera a URL no navegador - tudo sem recarregar o documento. Para o Google Analytics, essa mudança é completamente opaca: não há evento de load da janela, não há execução do snippet embutido, não há pageview registrado.
A consequência prática é devastadora para a análise: um usuário que entra pela home, navega por três páginas de produto e converte em um formulário de contato gera no GA4 apenas um pageview (a home) e o evento de conversão - sem nenhum registro das etapas intermediárias. A análise de caminho fica impossível, os relatórios de páginas mais visitadas ficam distorcidos (porque só contabilizam entradas diretas ou primeiras páginas de sessão), e métricas como taxa de rejeição perdem significado. Esse é exatamente o gap que a instrumentação manual resolve: ouvir mudanças de rota e disparar eventos pageview programaticamente.
SSR, SSG e CSR: como cada modo afeta o rastreamento
Next.js oferece três estratégias de renderização que coexistem em um mesmo projeto, e cada uma delas impacta o momento e o contexto em que o código de analytics pode executar. No SSG (Static Site Generation), as páginas são geradas em build time e servidas como HTML estático - o script do GA4 é embutido nesse HTML e executa normalmente no primeiro carregamento. No SSR (Server-Side Rendering), cada request gera HTML dinamicamente no servidor, mas o resultado é o mesmo: o snippet é enviado ao cliente e executa no navegador. Já no CSR puro (Client-Side Rendering), a página inicial é um HTML mínimo e todo o conteúdo é gerado via JavaScript após hydration - aqui o script do GA4 também executa, mas depende de que o bundle JS já tenha sido carregado e processado.
A questão crítica não é tanto o modo de renderização da página em si, mas sim o comportamento após a primeira renderização. Independente de a página ter sido gerada via SSG, SSR ou CSR, as navegações subsequentes são sempre client-side - e é nesse ponto que o rastreamento falha se não houver instrumentação adicional. Além disso, projetos que usam React Server Components no App Router introduzem uma camada extra de complexidade: componentes que rodam exclusivamente no servidor não têm acesso a APIs do browser como window.gtag, exigindo que o código de analytics seja isolado em Client Components com a diretiva 'use client'.
Métodos de implementação do GA4 em projetos Next.js
Existem três abordagens principais para integrar o Google Analytics 4 em aplicações Next.js, cada uma com trade-offs entre controle, facilidade de manutenção e flexibilidade. A escolha depende da arquitetura do projeto (App Router ou Pages Router), do nível de customização necessário nos eventos e da familiaridade da equipe com gerenciadores de tags. Todas as abordagens partem do mesmo princípio: injetar o script gtag.js ou o Google Tag Manager no documento e garantir que mudanças de rota disparem eventos manualmente.
Via Script do Next.js com estratégia afterInteractive
O componente next/script é a forma nativa e recomendada de carregar scripts externos no Next.js, oferecendo controle sobre a estratégia de carregamento. Para o GA4, a abordagem mais comum é usar strategy="afterInteractive", que carrega o script assim que a página se torna interativa - após o hydration, mas antes de bloquear a renderização. A implementação típica coloca o script no layout raiz (_app.js no Pages Router ou layout.tsx no App Router):
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html>
<head>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}`}
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}');
`}
</Script>
</head>
<body>{children}</body>
</html>
)
}
Essa implementação resolve o carregamento inicial, mas não rastreia navegações client-side. Para isso, é necessário adicionar um listener de mudanças de rota em um componente separado que use o hook useRouter (no Pages Router) ou usePathname (no App Router). A vantagem dessa abordagem é o controle total sobre o código executado e a ausência de dependência de plataformas terceiras. A desvantagem é que qualquer mudança em tags ou eventos exige deploy de código.
Via Google Tag Manager no App Router
Google Tag Manager (GTM) oferece uma camada de abstração que permite gerenciar tags, triggers e variáveis via interface web, sem necessidade de alterar código. Para Next.js, a integração do GTM container segue padrão similar ao gtag.js, mas injeta o script do Tag Manager ao invés do Analytics diretamente:
<Script
id="gtm-script"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
(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');
`,
}}
/>
No GTM, é necessário criar uma tag do tipo "Google Analytics: GA4 Configuration" e configurá-la para disparar em visualizações de página (pageview). A chave aqui é que o GTM não detecta automaticamente mudanças de rota do Next.js - é preciso disparar eventos customizados no dataLayer sempre que a rota mudar, e criar triggers no GTM que escutem esses eventos. Essa abordagem é ideal para projetos que já usam GTM extensivamente e precisam gerenciar múltiplas tags (analytics, pixels de ads, ferramentas de CRO) de forma centralizada.
Implementação manual com gtag.js e eventos customizados
Para máximo controle e rastreamento granular, especialmente em projetos com eventos de conversão complexos, a implementação manual com gtag.js diretamente no código é a mais flexível. Essa abordagem cria uma camada de abstração (lib/gtag.js por exemplo) que encapsula as chamadas ao gtag:
// lib/gtag.js
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID
export const pageview = (url) => {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: url,
})
}
export const event = ({ action, category, label, value }) => {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
})
}
Essa camada é importada em componentes e hooks que precisam rastrear eventos, permitindo chamadas como gtag.event({ action: 'form_submit', category: 'Contact', label: 'Footer Form' }) diretamente no código React. A vantagem é rastreamento explícito, testável e versionado com o restante do código. A desvantagem é que a equipe precisa ter conhecimento técnico para instrumentar novos eventos - não há interface visual para configuração.
Como rastrear mudanças de rota dinâmicas no Next.js
O rastreamento de navegação client-side é o ponto crítico da implementação do GA4 em Next.js, e a estratégia difere significativamente entre o Pages Router (arquitetura clássica) e o App Router (introduzido na versão 13). Ambos requerem instrumentação manual, mas os hooks e eventos disponíveis são distintos. A ideia central é sempre a mesma: detectar quando a URL muda, e disparar um evento pageview para o GA4 via gtag('config') ou dataLayer.push().
Usando useRouter e useEffect para disparar pageviews
No Pages Router, o hook useRouter expõe eventos de roteamento que podem ser ouvidos para rastrear mudanças de página. A implementação mais robusta coloca um listener no componente _app.js, que envolve toda a aplicação:
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import * as gtag from '../lib/gtag'
export default function App({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url) => {
gtag.pageview(url)
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return <Component {...pageProps} />
}
O evento routeChangeComplete dispara após a nova rota ter sido renderizada, garantindo que o pageview seja registrado com a URL correta. Usar routeChangeStart causaria inconsistências, porque o GA4 registraria a transição antes da página de destino estar pronta. É crucial fazer o cleanup do listener no return do useEffect para evitar múltiplas subscrições em hot reload durante desenvolvimento.
No App Router, não há o objeto router.events. A abordagem requer o hook usePathname do next/navigation e um useEffect que observa mudanças no pathname:
'use client'
import { usePathname, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
import * as gtag from '@/lib/gtag'
export function Analytics() {
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
const url = pathname + searchParams.toString()
gtag.pageview(url)
}, [pathname, searchParams])
return null
}
Esse componente Analytics deve ser incluído no layout raiz como Client Component (note a diretiva 'use client'), porque React Server Components não têm acesso a hooks de navegação. A dependência em [pathname, searchParams] garante que mudanças tanto na rota quanto em query strings disparem novos pageviews.
App Router vs Pages Router: diferenças na abordagem
As diferenças arquiteturais entre os dois sistemas de roteamento impactam diretamente a implementação de analytics. No Pages Router, o ciclo de vida é mais explícito: eventos de rota são disparados globalmente via router.events, e componentes têm acesso garantido a APIs do navegador porque sempre rodam no cliente (a menos que estejam em getServerSideProps ou getStaticProps, que são funções server-side). Isso simplifica a instrumentação - um único listener em _app.js resolve o rastreamento de navegação para toda a aplicação.
No App Router, o paradigma de React Server Components torna tudo mais granular. Componentes por padrão rodam no servidor, e qualquer código que dependa de APIs do browser (window, document, hooks de navegação) precisa ser marcado explicitamente como Client Component com 'use client'. Isso significa que o código de analytics não pode viver diretamente no layout.tsx raiz se esse layout for um Server Component - é necessário extrair a lógica de rastreamento para um componente separado que declara 'use client' e é importado no layout.
Além disso, o App Router usa streaming e Suspense, o que pode causar múltiplos disparos de useEffect