Seu GA4 no Next.js pode estar quebrado e você não sabe
Descobri que meu Google Analytics não coletava nada há meses por causa de dois erros comuns em App Router. Como identificar e corrigir de vez com @next/third-parties.
Passei meses achando que meu portfolio coletava dados de acesso. Não coletava. O script estava lá, o ID do GA4 estava correto, a tag aparecia no HTML — e mesmo assim o relatório vinha vazio.
O diagnóstico revelou dois problemas que aparecem com frequência em projetos Next.js com App Router. Se você copiou o snippet do Google e colou num componente React, vale conferir se não está no mesmo barco.
Problema 1: aspas escapadas viram JavaScript inválido
O snippet do GA4 usa aspas simples. Ao colar esse código dentro de JSX — especialmente passando por editores ou ferramentas que sanitizam HTML — as aspas podem virar entidades HTML:
<script>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXX');
`}
</script>
Repare no '. No HTML renderizado isso até parece uma aspa, mas dentro de um bloco <script> o navegador não decodifica entidades: ele lê literalmente ' e joga um SyntaxError. O script morre antes da primeira linha executar.
Pior: o erro acontece silenciosamente para quem não abre o console. A tag do Google carrega normalmente, você vê a requisição para googletagmanager.com na aba Network e conclui que está tudo certo. Mas o gtag('config', ...) nunca roda, então nenhum pageview é registrado.
Outro detalhe: um <script> escrito assim em JSX não executa como você espera no React. Para injetar script inline é preciso usar dangerouslySetInnerHTML ou o componente next/script.
Problema 2: confundir GA4 com GTM
O segundo erro é mais sutil. Existem dois produtos diferentes com snippets parecidos:
- Google Analytics 4 usa IDs no formato
G-XXXXXXX - Google Tag Manager usa IDs no formato
GTM-XXXXXXX
No meu caso, o mesmo ID G-... estava sendo usado nos dois snippets — o do gtag.js e o do GTM. O segundo tentava carregar um container que não existe, e o <noscript> com iframe do GTM apontava para o mesmo lugar. Requisições desperdiçadas para uma configuração inexistente.
Você não precisa dos dois. GTM é uma camada de gerenciamento que faz sentido quando há várias tags de marketing para orquestrar sem tocar no código. Para um site pessoal ou um projeto pequeno, o GA4 direto resolve.
A solução: @next/third-parties
O Next.js tem um pacote oficial que encapsula tudo isso com a estratégia de carregamento correta:
npm install @next/third-parties
No seu app/layout.tsx:
import { GoogleAnalytics } from "@next/third-parties/google";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="pt-br">
<body>{children}</body>
<GoogleAnalytics gaId="G-XXXXXXX" />
</html>
);
}
É isso. Sem script inline, sem aspas para escapar, sem risco de erro de sintaxe. O componente cuida do carregamento com afterInteractive e não bloqueia a renderização — o que também ajuda no LCP.
Eventos que importam mais que pageview
Já que estamos aqui: pageview sozinho não responde nada de útil. O que importa é saber o que a pessoa faz. O mesmo pacote exporta sendGAEvent:
"use client";
import { sendGAEvent } from "@next/third-parties/google";
export function ProjectLink({ project, url }: { project: string; url: string }) {
return (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
onClick={() => sendGAEvent("event", "project_click", { project })}
>
Ver projeto
</a>
);
}
Note o "use client": o handler roda no navegador, então o componente precisa ser client component.
Num portfolio, os eventos que valem a pena são poucos e específicos:
project_click— qual projeto despertou interesse e para onde a pessoa foicontact_submit— a conversão de verdadecv_download— intenção de contrataçãosocial_click— para onde o tráfego escapa
Depois de criar os eventos, entre no painel do GA4 em Administrador → Eventos e marque o de conversão como key event. Sem essa marcação, ele fica perdido no meio dos outros.
Como verificar se funcionou
Três checagens rápidas, do mais barato ao mais confiável:
- Console do navegador. Abra o site e digite
window.dataLayer— deve retornar um array populado, nãoundefined. - Aba Network. Filtre por
collecte recarregue a página. Deve aparecer uma requisição paragoogle-analytics.com/g/collectcom o seu ID. - Tempo real no GA4. Abra seu site numa aba anônima e veja se aparece em Relatórios → Tempo real em até um minuto.
O terceiro é o único que prova o caminho completo. Os dois primeiros mostram que o script rodou, mas só o painel confirma que o dado chegou.
A lição
O erro sobreviveu tanto tempo porque falhava de forma silenciosa. Nada quebrava visualmente, a tag existia no HTML, e o painel vazio parecia apenas "site sem visitas" — uma explicação plausível o suficiente para nunca ser investigada.
Vale a pena ter o hábito de conferir o tempo real do GA4 depois de qualquer deploy que mexa em analytics. Leva trinta segundos e evita meses de dados perdidos.
Gostou do artigo?
Estou sempre aberto a conversar sobre projetos, oportunidades e ideias.
Falar comigo