Devlog · Fase 0 · 7/7
SEO multilíngue e GEO: 6 locales, SSR e extração por IA
Seis URLs indexáveis por língua, um hreflang BCP47 bidirecional, um grafo JSON-LD ligado por @id e um llms.txt: como aubia.dev se dá a ler aos motores e às IAs.
- seo
- i18n
- geo
- inertia

Uma vitrine pré-lançamento tem um único trabalho do lado da visibilidade: ser encontrável, em várias línguas, pelos motores clássicos e pelos motores generativos que agora respondem no lugar dos dez links azuis. aubia.dev é servida em seis línguas, em rendering de servidor, com uma marcação pensada para a extração. Este último artigo da série desmonta essa mecânica. E o blog que você lê roda sobre as mesmas regras.
Seis locales, uma URL por língua
O site é roteado sob um prefixo de locale: /fr, /en, /es, /de, /it, /pt. Cada língua tem, portanto, sua própria URL indexável, nunca um parâmetro ou um cookie que ocultaria o conteúdo dos robôs. Um middleware resolve o locale antes do roteamento, a partir do segmento de URL, do cookie, depois do cabeçalho Accept-Language. A raiz / redireciona para a melhor língua detectada.
Para a audiência internacional fora das zonas cobertas, x-default aponta para o inglês. O rendering é feito em SSR via Inertia: o HTML completo sai no primeiro byte, o que é um invariante não negociável para o SEO. Um motor que visita uma página recebe seu conteúdo renderizado, não uma casca vazia à espera da hidratação JavaScript.
Essas seis línguas não têm todas o mesmo status, e eu assumo a hierarquia. O francês é minha fonte editorial, escrita à mão. O inglês é um pivô traduzido manualmente, porque é a língua da minha audiência internacional e não tolera o mais ou menos. O espanhol, o italiano, o alemão e o português são então traduzidos automaticamente a partir desse pivô inglês. E as copies mais críticas, o título do hero, o claim, os argumentos-chave, permanecem excluídas da tradução automática e passam sempre pela minha mão.
Por trás dessas seis línguas, não há nenhuma biblioteca i18n de terceiros. A solução npm mainstream se revelou incompatível com o React 19 e arriscava vazar estado entre as requisições sob o servidor long-running do Octane. O motor i18n cabe, portanto, em um hook caseiro de umas cinquenta linhas, conectado ao compartilhamento de props nativo do Inertia, sem uma única dependência acrescentada.
O hreflang, nos dois sentidos
O hreflang diz a um motor: esta página também existe nestas outras línguas, nestas URLs precisas. A regra que faz a maioria das implementações fracassar é a bidirecionalidade: cada versão deve referenciar a si mesma e referenciar todas as outras. Uso códigos BCP47 completos, mais precisos do que um código curto, para eliminar a ambiguidade de país entre pt-PT e pt-BR.
O sitemap dinâmico produz esse entrelaçamento para cada página publicada, com um link alternativo por locale:
foreach (self::LOCALE_TO_BCP47 as $altLocale => $bcp47) {
$altLoc = $baseUrl . '/' . $altLocale . $path;
$xml .= '<xhtml:link rel="alternate" hreflang="' . $bcp47 . '" href="' . $altLoc . '"/>';
}
Do lado da página, o componente SeoHead põe os mesmos sinais no <head>, mas apenas nas páginas indexáveis. As páginas efêmeras, como a confirmação da lista de espera, ficam em noindex: acrescentar ali um canonical ou um hreflang enviaria um sinal contraditório.
Um grafo JSON-LD ligado por @id
Os dados estruturados de Aubia formam um único @graph, onde cada entidade se referencia por um @id estável: a Organization, o WebSite, a SoftwareApplication e a Person fundadora se apontam mutuamente. Essa fiação ajuda os motores de busca e as IAs de busca a entender que esses objetos descrevem a mesma entidade, e não quatro coisas sem relação.
const jsonLd = JSON.stringify({
'@context': 'https://schema.org',
'@graph': [
{ '@type': 'Organization', '@id': `${SITE_URL}/#organization`, /* ... */ },
{ '@type': 'Person', '@id': `${SITE_URL}/#founder`, /* ... */ },
{ '@type': 'WebSite', '@id': `${SITE_URL}/#website`, /* ... */ },
...additionalJsonLd,
],
});
O ponto de extensão é additionalJsonLd: cada página injeta seus próprios nós no grafo compartilhado. A landing acrescenta ali o seu bloco FAQPage. Um artigo de blog acrescenta o seu BlogPosting e a sua trilha de navegação, sem tocar na base Organization/WebSite.
GEO: escrever para os motores que leem
Escrevi estas páginas para serem citadas, não apenas classificadas: quando uma IA responde no lugar dos dez links azuis, o que conta é que ela possa extrair uma passagem inteira sem deformá-la. Duas alavancas concretas em aubia.dev.
A primeira é um arquivo public/llms.txt na raiz: um resumo estruturado do produto, do seu posicionamento e do seu status, escrito para ser lido por uma máquina. Ele enuncia o que é Aubia, a sua diferença de cross-review, o seu público e o seu roadmap, em texto puro sem ruído de interface.
A segunda é uma regra de escrita, aplicada a este próprio artigo: seções autossuficientes. Cada título H2 abre com uma definição, depois se desenvolve num bloco de cerca de cento e cinquenta palavras que se sustenta sozinho, fora do contexto da página. Um motor que fatia a página em trechos precisa poder citar qualquer bloco sem que o sentido desmorone. Listas curtas e títulos explícitos terminam o trabalho.
O ciclo meta: este blog aplica suas próprias regras
Este blog é, ele mesmo, a demonstração de tudo o que precede. Ele é flat-file: cada artigo é um arquivo Markdown no repo, convertido em HTML no parsing, sem banco de dados, fiel ao partido da Fase 0. Publicar um artigo é fazer o merge de uma branch, nada mais.
A publicação é agendada pela data no front matter: o módulo mantém todos os artigos em cache, mas na requisição só serve aqueles cuja data já passou. Um artigo datado de uma segunda-feira aparece naquela segunda, sem redeploy, e permanece invisível antes: ausente do índice, do feed RSS, do sitemap, e em 404 na sua URL.
Cada artigo publicado ganha sua entrada de sitemap com sua verdadeira data de última modificação, suas alternativas de língua, sua marcação BlogPosting e sua trilha de navegação. A mesma mecânica de seis locales, de hreflang bidirecional e de grafo JSON-LD que descrevi para a landing se aplica, artigo por artigo, a essas páginas.
Este foi o último capítulo da série sobre a construção da Fase 0. Construí toda a Fase 0 nesse nível de detalhe, e o seu feedback decidirá o que vem a seguir. A lista de espera é o lugar para acompanhá-la de perto.
Esta construção é contada aqui, artigo após artigo. Embarque: o seu feedback desenhará o que vem a seguir.
Entrar na lista de espera