A forma mais confiável de inserir JSON-LD no App Router é gerar diretamente um bloco `<script type="application/ld+json">` em um Server Component. Assim, os dados estruturados já aparecem no HTML inicial retornado pelo servidor, e o crawler de IA consegue lê-los sem executar JavaScript. Para mecanismos de resposta, o JSON-LD acrescentado depois via useEffect ou Google Tag Manager praticamente não existe: na maioria dos casos, o crawler termina a coleta e vai embora antes da hidratação da página.
Por que a geração precisa acontecer no servidor?
A diferença está no momento em que o conteúdo aparece. O Server Component grava o JSON-LD na string de HTML durante a requisição. Por isso, o primeiro arquivo coletado por crawlers como GPTBot, ClaudeBot, PerplexityBot e Google-Extended já contém o schema completo. A inserção no cliente, por outro lado, depende de o navegador baixar e executar o bundle — etapa que a maioria dos crawlers de IA nem sequer realiza. Há ainda uma armadilha fácil de ignorar: o Google Rich Results Test renderiza o JavaScript, então os dados inseridos no cliente parecem normais na ferramenta e dão a impressão de que a validação foi concluída. Na prática, porém, o mecanismo de resposta pode receber apenas o HTML original, ainda não renderizado e sem nenhum schema. Nas auditorias técnicas que fazemos para clientes, esse é um dos problemas mais comuns — e também um dos mais fáceis de passar despercebido.
Exemplo mínimo viável: uma única page.tsx
A implementação é curta. Não exige pacotes adicionais nem rotas de API: tudo acontece no Server Component que renderiza o artigo. O fluxo tem apenas três partes centrais: buscar os dados, montar os objetos e gerar o script.
- Em `app/blog/[slug]/page.tsx` — que, por padrão, é um Server Component — use primeiro `await` para buscar os dados do artigo.
- Monte um objeto JavaScript puro chamado `jsonLd`. Comece com `"@context": "https://schema.org"` e `"@type": "Article"`; depois, preencha dinamicamente os demais campos — headline, datePublished, author e image — com os dados do artigo.
- No início do JSX retornado, inclua: `<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, "\\u003c") }} />`.
- Não importe bibliotecas de cliente nem adicione "use client" no início do arquivo. Ao fazer isso, o arquivo passa a ser um Client Component e todo o benefício da geração no servidor é perdido.
Como conectar várias entidades com @graph
Uma página normalmente precisa de mais de um schema: a marca é uma Organization, o artigo é um Article, o caminho de navegação é um BreadcrumbList e a seção de perguntas e respostas é uma FAQPage. Em vez de gerar quatro tags de script independentes, uma solução mais organizada é reunir tudo em um array `@graph` dentro do mesmo objeto JSON-LD e conectar as entidades por meio de `@id`. Por exemplo: o `publisher` de Article pode apontar para o `@id` de Organization, enquanto o último item de BreadcrumbList aponta para o `@id` da página atual. Assim, o mecanismo de resposta obtém todo o mapa de relações entre as entidades em uma única análise, com menos risco de encontrar nós contraditórios ou duplicados.
- Organization e WebSite: coloque-os em `app/layout.tsx` para compartilhá-los uma única vez em todo o site. Não repita essas declarações em cada página.
- Article ou BlogPosting: coloque-o na `page.tsx` do artigo e preencha os campos dinamicamente com os dados da página atual.
- BreadcrumbList: organize-o de acordo com a hierarquia das rotas. Isso ajuda especialmente a IA a compreender a estrutura do site.
- FAQPage: adicione-o somente quando a página realmente exibir uma seção de perguntas e respostas. O texto de `acceptedAnswer` deve corresponder ao conteúdo visível. Não invente uma FAQ apenas para ter o schema.

Como manter o JSON-LD sustentável
Se os objetos de schema ficarem espalhados pelas páginas, depois de três meses ninguém vai querer mexer neles. Nossa abordagem é centralizá-los em `lib/schema.ts` e usar os tipos de `schema-dts` (`import type { Article, WithContext } from "schema-dts"`) para limitar o valor retornado por cada função geradora. Se o nome ou o tipo de um campo estiver incorreto, o TypeScript apontará o erro durante a compilação. A página apenas chama `buildArticleSchema(post)`, recebe um objeto com segurança de tipos e o gera. Qualquer alteração passa a valer em todo o site. As URLs devem sempre ser absolutas e ficar centralizadas em uma constante `SITE_URL`, evitando divergências de `@id` entre o site oficial e o domínio de preview.
Erros comuns e validação antes da publicação
- Os valores de `url`, `image` e `datePublished` no JSON-LD não correspondem ao conteúdo real da página. O mecanismo de resposta identifica essa divergência, o que reduz a confiança na página inteira.
- Usar caminhos relativos em `@id` ou `url` pode apontar para o endereço errado ao alternar para o domínio de preview.
- Esquecer de escapar `<` ou adicionar `"use client"` por engano no início da página faz com que o script passe a ser gerado no cliente.
- Antes da publicação, use o Google Rich Results Test para verificar a sintaxe. Depois, execute `curl` para capturar diretamente o HTML original da página e confirmar que o script realmente faz parte da resposta inicial — e não aparece apenas no DevTools do navegador. Essa é a etapa mais importante, pois reproduz exatamente o que o crawler de IA enxerga.
Nos sites que auditamos, a maioria dos problemas de JSON-LD não vem de um schema mal escrito, mas do fato de ele existir apenas no cliente. Se os dados não estão no HTML retornado pelo servidor, para a IA é como se não existissem.— Tenten GEO Technical Audit
Da implementação à citação
Levar o JSON-LD para o Server Component é o requisito mínimo para tornar o conteúdo legível por máquinas. O próximo passo é alinhar a nomenclatura das entidades, os links internos e a consistência entre páginas, para que a IA possa citar sua marca de forma recorrente como uma fonte confiável. Se você não sabe se os dados estruturados do seu site chegaram ao HTML que o crawler realmente vê, agende um diagnóstico GEO de 30 minutos. Vamos analisar o código-fonte efetivamente capturado para mapear as lacunas, em vez de olhar apenas para a página já renderizada.



