← Todos os artigos
4 min de leitura

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.

Next.jsGA4SEO

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(&#x27;js&#x27;, new Date());
  gtag(&#x27;config&#x27;, 'G-XXXXXXX');
  `}
</script>

Repare no &#x27;. No HTML renderizado isso até parece uma aspa, mas dentro de um bloco <script> o navegador não decodifica entidades: ele lê literalmente &#x27; 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 foi
  • contact_submit — a conversão de verdade
  • cv_download — intenção de contratação
  • social_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:

  1. Console do navegador. Abra o site e digite window.dataLayer — deve retornar um array populado, não undefined.
  2. Aba Network. Filtre por collect e recarregue a página. Deve aparecer uma requisição para google-analytics.com/g/collect com o seu ID.
  3. 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