Na auditoria do Lampyx, 107 dos 108 posts tinham pelo menos uma imagem com alt="" no corpo do artigo. As imagens destacadas estavam certas. O problema era a imagem dentro do texto, e a biblioteca de mídia já tinha o texto alternativo de cada arquivo. O alt estava guardado, mas não chegava na tag do post.

Abrir 107 posts um por um seria repetitivo demais. Fiz por script e conferi nas páginas publicadas. O código está abaixo.

Por que isso importa

O Google usa o texto alternativo, junto com o conteúdo da página, para entender do que se trata a imagem. Leitores de tela leem esse texto para quem não enxerga a imagem. A recomendação do Google é escrever uma descrição útil e que combine com o assunto da página, sem encher de palavra-chave. O exemplo dele: alt="Dalmatian puppy playing fetch" é melhor que alt="puppy", e uma imagem sem alt é o exemplo ruim.

Como achei o problema

Li o corpo de cada post e contei as tags <img> com alt vazio. Nos meus posts, o editor de blocos grava a imagem assim (aqui como estava, com o alt vazio):

<img src="..." alt="" class="wp-image-619"/>

O número depois de wp-image- é o ID do arquivo na biblioteca de mídia. É por ele que dá para buscar o alt que já existe.

O script

Roda no console do navegador (F12), numa página do painel de administração, com você logado como administrador. A autenticação por cookie da API do WordPress exige um nonce no cabeçalho X-WP-Nonce. Sem ele a API trata a requisição como visitante e recusa a edição.

const nonce = (await (await fetch('/wp-admin/admin-ajax.php?action=rest-nonce')).text()).trim();
const api = (caminho, opcoes = {}) =>
  fetch('/wp-json/wp/v2/' + caminho, {
    ...opcoes,
    headers: { 'X-WP-Nonce': nonce, 'Content-Type': 'application/json' },
  }).then(r => r.json());

const esc = s => s.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');

function preencherAlt(html, altPorId) {
  return html.replace(/<img\b[^>]*>/g, tag => {
    const m = tag.match(/wp-image-(\d+)/);
    if (!m || !altPorId[m[1]] || !/\salt=""/.test(tag)) return tag;
    return tag.replace(/\salt=""/, ` alt="${esc(altPorId[m[1]])}"`);
  });
}

async function corrigirPost(id, aplicar = false) {
  const post = await api(`posts/${id}?context=edit&_fields=id,content`);
  const html = post.content.raw;
  const ids = new Set();
  for (const tag of html.match(/<img\b[^>]*>/g) ?? []) {
    const m = tag.match(/wp-image-(\d+)/);
    if (m && /\salt=""/.test(tag)) ids.add(m[1]);
  }
  const altPorId = {};
  for (const imgId of ids) {
    altPorId[imgId] = (await api(`media/${imgId}?_fields=id,alt_text`)).alt_text;
  }
  const novo = preencherAlt(html, altPorId);
  if (novo === html) return false;
  if (aplicar) {
    await api(`posts/${id}`, { method: 'POST', body: JSON.stringify({ content: novo }) });
  }
  return true;
}

Primeiro rode com aplicar = false, que só conta quantos posts mudariam e não grava nada:

let lista = [];
for (const pg of [1, 2]) {
  const r = await api(`posts?per_page=100&page=${pg}&_fields=id`);
  if (Array.isArray(r)) lista = lista.concat(r);
}
let mudaria = 0;
for (const p of lista) if (await corrigirPost(p.id)) mudaria++;
console.log(lista.length, 'posts,', mudaria, 'mudariam');

Se o número bater com o esperado, troque para corrigirPost(p.id, true) e rode de novo.

Como validei que só o alt mudou

Antes de gravar, comparei o HTML novo com o antigo depois de tirar todos os atributos alt dos dois. Se os dois ficam iguais, a única diferença é o alt. Em código:

const semAlt = s => s.replace(/\salt="[^"]*"/g, '');
semAlt(novo) === semAlt(html); // tem que ser true

O WordPress guarda revisões de cada post. Se algo sair errado, dá para voltar para a versão anterior pelo editor.

Como confirmei no ar

Depois da gravação e da limpeza do cache, baixei cada uma das 108 páginas como visitante e contei as imagens com alt vazio dentro do conteúdo do artigo. Deu zero. Rodei a versão sem gravação do script de novo e ela também retornou zero posts a corrigir.

O que o script não resolve

  • Usei o texto que já estava na biblioteca de mídia. Eu não reescrevi nenhum. Se o texto de lá é ruim, o alt do post fica ruim. Leia os principais e melhore à mão.
  • Imagem sem o atributo alt na tag, e não só vazio, não entra nesse script. O meu caso era alt="".
  • Imagem que não tem o ID no class (colada de fora do editor) também fica de fora.
  • Imagem que só enfeita e não informa nada pode ficar com alt vazio de propósito. Aqui eram imagens com conteúdo, então o vazio era erro.

O que fazer agora

  1. Rode a contagem com aplicar = false no seu site.
  2. Leia uma amostra dos textos da biblioteca de mídia antes de aplicar.
  3. Aplique, limpe o cache e confira as páginas como visitante.
  4. Daqui para frente, preencha o alt no upload. É uma linha por imagem.

Eu fiz essa correção junto com a configuração do Yoast, que contei em Yoast SEO: a configuração que usei em 108 posts.

Fontes: Google Search Central, boas práticas para imagens e WordPress, autenticação da API REST.

Sobre o Fagulha

Sou o Fagulha, curioso por tecnologia sem formação técnica na área. Escrevo o Lampyx tentando traduzir assunto complicado em texto que qualquer pessoa entende, sem enrolação e sem jargão desnecessário. Conheça minha história completa aqui.

Toda ideia boa começa com uma fagulha.