A Google deixou de reparar os seus dados estruturados. A extração de JSON-LD aplica agora uma só passagem de unescaping de HTML, por isso um bloco que antes era endireitado em silêncio simplesmente não faz parse e desaparece. Não há erro na Search Console, não há aviso, há apenas um resultado enriquecido que deixa de aparecer. Este texto mostra como medir o seu corpus em poucos minutos em vez de adivinhar, de onde vem essa escrita no WordPress e porque uma auditoria pontual não chega. A nossa própria medição sobre 68 055 blocos está aqui dentro, com o script.
O que a Google mudou de facto
A declaração é curta e merece citação integral, porque tudo o resto decorre de uma frase:
To bring our parser up to JSON and other standards, we changed our JSON-LD extraction and are now only applying a single pass of HTML unescaping
Gary Illyes acrescentou onde está definida a correção: RFC 8259, a especificação do JSON. Não é uma recomendação de SEO, é a referência a uma norma que qualquer parser de JSON segue.
A consequência prática vem igualmente curta: as entidades com duplo escape, como & ou ✔, deixam de ser desdobradas. Antes o parser fazia uma passagem extra e endireitava aquilo. Agora faz uma passagem e fica com texto que não é JSON válido.
Vale nomear desde já o que falta nesta informação. Não há data de lançamento. Não há ligação para documentação atualizada da Google. A fonte é uma publicação no LinkedIn, noticiada pelo Search Engine Roundtable a 21 de agosto de 2026. Trate-a como um estado descrito pela Google e não como uma especificação que possa colocar diante de um cliente.
O que é o duplo escape e de onde vem
Veja um caso simples: o nome de empresa «Silva & Filhos» no campo name de um bloco JSON-LD.
| escrito como | o que o parser de JSON vê | estado |
|---|---|---|
"Silva & Filhos" | Silva & Filhos | válido, o JSON não exige escapar o E comercial |
"Silva & Filhos" | Silva & Filhos | válido, escape universal |
"Silva & Filhos" | Silva & Filhos | faz parse, mas o valor está errado |
"Silva & Filhos" | Silva & Filhos | isto é duplo escape |
Um E comercial sozinho não rebenta o bloco, porque o JSON não exige escapá-lo. O grave começa nas aspas. Se o template escrever " onde devia estar \", depois de uma passagem sobra " e não umas aspas. A string não fecha e o bloco deixa de ser JSON.
De onde vem isto no WordPress? Quase sempre de processar duas vezes o mesmo valor. O conteúdo passa por esc_html() ao gravar, por um filtro do tema ao apresentar, e acaba num campo JSON-LD que teria escapado bem por si próprio. Cada passo isolado está certo. Compostos, produzem uma escrita que funcionou durante anos apenas porque a Google a endireitava por nós.
Como verificar o seu corpus em cinco minutos
A regra mais importante: verifica-se o HTML construído, não a fonte do template. Na fonte parece tudo bem, porque o escape é acrescentado na saída. Se publica um build estático, analisa o diretório de saída. Num WordPress clássico, puxa uma amostra de URLs com wget ou curl e analisa o que o servidor respondeu.
A verificação em si são pouco mais de dez linhas. Extraia cada bloco <script type="application/ld+json">, tente fazer parse e procure à parte o padrão de entidade dupla:
const RE = /<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
let m;
while ((m = RE.exec(html))) {
const body = m[1];
if (/&(quot|amp|lt|gt|#\d+);/.test(body)) report("entidade dupla", file);
try { JSON.parse(body); } catch (e) { report("não faz parse: " + e.message, file); }
}
Dois testes separados, porque apanham coisas diferentes. O JSON.parse falha numa string partida, mas aceita sem problema &amp; dentro de um valor que continua a ser JSON válido e apenas contém lixo. O teste do padrão apanha precisamente esse segundo caso: o bloco faz parse e no resultado enriquecido aparece & onde devia estar um carácter.
Um terceiro teste que vale a pena juntar são as entidades HTML simples nos valores. Não são um erro, mas depois da mudança passam a ser desdobradas exatamente uma vez, pelo que o resultado pode diferir do que conhecia. Mais vale saber que estão lá.
A nossa medição: 68 055 blocos, zero falhas
Corremos esta análise sobre o nosso próprio corpus a 30 de agosto de 2026, contra uma versão de produção acabada de construir.
| métrica | resultado |
|---|---|
| páginas com pelo menos um bloco JSON-LD | 15 742 |
| blocos JSON-LD no total | 68 055 |
| blocos que não fazem parse como JSON | 0 |
| páginas com entidade de duplo escape | 0 |
| páginas com entidade HTML simples dentro do JSON-LD | 0 |
Zero nas três categorias. Não escrevemos isto como gabarolice, mas como informação sobre o que este resultado significa e o que não significa. A nossa stack gera dados estruturados a partir do frontmatter através de componentes Astro e insere-os com o padrão set:html={JSON.stringify(...)}. O JSON.stringify produz JSON válido por definição e o set:html não acrescenta escape de HTML próprio. Por outras palavras: não tivemos zero por sermos cuidadosos, tivemos porque aquele padrão em concreto não tem forma de produzir duplo escape.
Isso importa mais do que o número. Se a sua stack monta o JSON-LD concatenando strings no template, ou através de um plugin que cola um campo de conteúdo dentro de JSON já preparado, o risco é real e o seu resultado será outro. Meça o seu, não copie o nosso.
Onde parte mais vezes no WordPress
Das auditorias que fazemos em clientes saem três origens recorrentes.
A primeira é um plugin de SEO que preenche description a partir de um campo que já passou por wp_kses ou esc_attr. Costuma partir no apóstrofo e nas aspas, e em português juntam-se ainda as aspas angulares que muitos sistemas editoriais inserem automaticamente.
A segunda é um bloco JSON-LD escrito à mão e colado em header.php ou nas opções do tema, onde os valores entram com echo e sem wp_json_encode. É a variante habitual em temas feitos por medida há alguns anos e a mais difícil de encontrar, porque não aparece em nenhum ecrã de plugin.
A terceira é um construtor de páginas que guarda o conteúdo com entidades HTML já na base de dados. Nessa altura até um gerador de JSON-LD bem escrito recebe texto com & à entrada e codifica-o obedientemente uma segunda vez.
O denominador comum é sempre o mesmo: um valor escapado duas vezes, uma para HTML e outra para JSON, por duas camadas que não sabem uma da outra.
Como codificar isto bem
A regra é inequívoca porque existe uma norma para ela. Dentro de um valor JSON escapa-se à maneira do JSON e não à maneira do HTML.
Em PHP isso significa wp_json_encode() sobre a estrutura toda, nunca strings montadas à mão. Em JavaScript, JSON.stringify(). Num template Astro, o padrão que usamos:
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
Se precisar mesmo de um carácter capaz de fechar o bloco de script antes do tempo, use um escape universal. & e < são seguros em qualquer parser de JSON e não exigem conhecimento de HTML a quem lê os dados.
O que não fazer: não coloque uma entidade HTML dentro de um valor JSON à espera que alguém a desdobre. Durante anos esse alguém foi a Google. A partir de agora desdobra exatamente uma vez, e qualquer outro consumidor dos seus dados estruturados, do Bing a um assistente de IA, nunca teve essa obrigação.
Uma auditoria pontual não chega, transforme-a num portão
Esta é a parte que mais se salta. Os dados estruturados não são um texto que se escreve uma vez. São gerados por um template, um plugin ou uma integração, e qualquer atualização pode trazer o escape de volta. Uma auditoria feita hoje fala do build de hoje e de mais nada.
Transformámos a análise num portão que corre depois do build. Lê o diretório de saída, conta blocos, tenta fazer parse de cada um e falha apenas perante um problema real, ou seja, um bloco que não faz parse ou uma entidade dupla. Se o diretório de saída não existir, termina a zero, para não produzir um vermelho falso num ambiente onde ainda ninguém construiu.
Três pormenores decidem se um portão destes vale alguma coisa. Primeiro, tem de ler o artefacto e não a fonte, porque a fonte não prova nada sobre escape. Segundo, tem de contar blocos e mostrar o número, para que alguém repare que cai de sessenta e oito mil para duzentos porque uma integração deixou de os gerar. Terceiro, tem de separar erro de aviso: as entidades HTML simples são reportadas mas não deitam o build abaixo, porque não são uma avaria, são algo que convém saber.
O que fazer quando a análise encontra alguma coisa
Uma análise dá uma lista de ficheiros, não um diagnóstico. Antes de corrigir seja o que for, descubra que camada produz aquela escrita, porque uma correção no sítio errado volta com a atualização seguinte.
Pegue num URL da lista e veja a resposta crua do servidor, por exemplo com curl -s URL | grep -A5 "application/ld+json". Confirme se o valor danificado vem do título do artigo, da descrição SEO ou de um campo personalizado. Isso aponta a camada mais depressa do que ler código.
Depois decide conforme a origem:
- Se o valor vem de um plugin de SEO, verifique se tem dois plugins a gerar o mesmo tipo de schema. Um gerador duplicado causa valores estranhos com mais frequência do que uma falha em qualquer um deles isolado.
- Se o bloco está no tema, reescreva-o com
wp_json_encode()sobre o array completo. A concatenação manual de strings é aqui o único erro verdadeiro e não admite remendos parciais. - Se as entidades já estão na base de dados porque um construtor de páginas as pôs lá, não corrija no gerador. Descodifique o valor uma vez antes de o passar ao codificador JSON, por exemplo com
html_entity_decode()com a flag de aspas e UTF-8 explícito.
O erro clássico é assim:
echo '{"name":"' . esc_html( $title ) . '"}';
O correto é assim:
echo wp_json_encode( array( 'name' => $title ) );
A diferença não está no número de caracteres, está em quem trata do escape. Na primeira versão trata dele uma função de HTML num contexto que não é HTML. Na segunda trata dele um codificador JSON em contexto JSON.
Depois da correção, analise um build novo e não o artefacto antigo. Parece óbvio, e metade dos relatos de «corrigi e continua partido» é uma análise sobre um diretório de saída desatualizado.
O que perde mesmo se ignorar
Perder schema não dói de imediato, e é isso o pior. Não há queda de posições de um dia para o outro, há um desaparecimento gradual daquilo que destacava o resultado: estrelas de avaliação, dados de produto, uma lista de perguntas frequentes, migalhas de navegação. O efeito vê-se na taxa de cliques e não na posição, e a taxa de cliques desce devagar e é fácil atribuí-la a outra coisa.
O segundo destinatário destes dados é mais recente e menos tolerante. Os motores de resposta leem dados estruturados porque é a via mais barata para estabelecer do que trata uma página sem interpretar o texto todo. No nosso site o tráfego de agentes já é mensurável e não é marginal: na ordem de cem visitas por dia, dois terços através do nosso próprio endpoint MCP. Esses sistemas não têm motivo para reparar o escape alheio. A Google reparou-o durante anos por cortesia para com a web que encontrou. Um consumidor novo dos seus dados nunca teve esse hábito.
A terceira camada é a Search Console, e aqui convém dizer o que não vai ficar a saber. Os relatórios de resultados enriquecidos mostram uma queda de elementos válidos, mas não dizem «este bloco não fez parse por causa de uma entidade dupla». Vê que faltam elementos e terá de descobrir porquê sozinho. É por isso que a análise do seu lado vale esses minutos: dá a causa e não apenas o sintoma.
O que não sabemos e o que fazer mesmo assim
Não conhecemos a data de lançamento. Não sabemos se a mudança atinge todos os tipos de schema por igual, nem se a Search Console chega a reportar uma entidade perdida em vez de simplesmente deixar de mostrar o resultado enriquecido. Não há documentação atualizada para onde remeter um cliente. São lacunas reais e é melhor dizê-lo do que dar a uma publicação no LinkedIn a categoria de especificação.
Apesar das lacunas, a decisão operacional é simples e não depende de nenhuma das informações em falta. O JSON válido já era válido quando a Google corrigia os erros por si. Analise o seu HTML construído, repare o que não faz parse, troque as entidades HTML dos valores por escapes JSON e ancore o teste no processo para que não volte. Se o resultado der zero, como o nosso, também isso é um resultado: sabe que o seu gerador não tem forma de produzir esta falha e deixa de pensar nisso a cada atualização de plugin.
O maior risco desta história não está na mudança, está em ela ser silenciosa. Não chega nenhum alerta. O resultado enriquecido deixa simplesmente de aparecer e, três meses depois, um relatório mostra uma queda de visibilidade sem causa evidente. Cinco minutos de análise hoje saem mais baratos do que essa investigação.





