Voltar ao blog
nuxtseogeoaeoschema-orgog-imagellms-txtvue

SEO, GEO e AEO na prática: preparando um site Nuxt para o Google e para as IAs

08 de julho de 2026 às 18:12 - Por Larissa Santos

SEO, GEO e AEO na prática: preparando um site Nuxt para o Google e para as IAs

Um site hoje não é lido por um público só. A mesma página é rastreada pelo buscador do Google, lida pelo mecanismo que monta a resposta direta no topo da busca, e visitada pela IA generativa que vai decidir se cita aquele conteúdo como fonte. Cada um lê de um jeito, e otimizar para um não garante os outros.

É daí que saem as três siglas. SEO é a otimização clássica, indexar e ranquear no buscador. AEO (Answer Engine Optimization) prepara o conteúdo para virar resposta direta, aquele bloco que aparece no topo da busca ou no AI Overview. E GEO (Generative Engine Optimization) é a visibilidade dentro das respostas de IAs como ChatGPT e Perplexity. As três se cruzam, mas cada uma resolve um tipo de leitura diferente da mesma URL.

A boa notícia é que num projeto Nuxt, com o módulo @nuxtjs/seo, dá para cobrir as três frentes com ferramenta nativa. Neste artigo vamos ver como, começando pela base e passando por og:image dinâmica, Schema.org, robots.txt, llms.txt e sitemap.


A base: SSR e meta tags por página

Antes de qualquer og:image ou schema, existe uma condição que sustenta tudo: o conteúdo precisa estar no HTML que sai do servidor. Se a página só se monta depois do JavaScript rodar no navegador, boa parte dos crawlers recebe um HTML quase vazio, com o <div id="app"> e pouco mais que isso. Isso vale para o Google, que hoje até executa JavaScript mas com atraso e limites, e vale ainda mais para os crawlers de IA, que muitas vezes leem só o HTML cru.

É aqui que o Nuxt entra na frente de uma SPA tradicional. Com SSR ativo, os dados já vêm renderizados dentro do HTML da resposta, junto com as meta tags e o dado estruturado. O crawler não depende de executar nada para enxergar o conteúdo. Para páginas que não mudam a cada request, dá ainda para pré-renderizar em build com o prerender, entregando HTML estático e rápido.

Com a renderização resolvida, o próximo passo é o básico do SEO técnico: title, description e as meta tags de cada página. O useSeoMeta, chamado dentro do <script setup> da página, cuida disso de forma tipada, alimentado pelos dados reais da rota:

ts
const seoTitle = post.value.seoTitle ?? post.value.title
const seoDescription = post.value.seoDescription ?? post.value.description

useSeoMeta({
  title: seoTitle,
  description: seoDescription,
  ogTitle: seoTitle,
  ogDescription: seoDescription,
  ogUrl: `https://www.larisantos.com.br/blog/${route.params.slug}`,
  ogImage: toAbsoluteUrl(post.value.image),
  ogImageAlt: post.value.title,
  twitterTitle: seoTitle,
  twitterDescription: seoDescription,
})

Repare que ogTitle e twitterTitle recebem o mesmo valor do title. É comum esquecer esses campos e acabar com o Twitter Card mostrando o texto da home em todas as páginas, um bug silencioso que só aparece quando o link é compartilhado.

Falta um detalhe que evita conteúdo duplicado: o canonical. Ele declara qual é a URL oficial daquela página, para o buscador não tratar variações como páginas diferentes. Ainda na mesma página, ele vai pelo useHead:

ts
useHead({
  link: [
    {
      rel: "canonical",
      href: `https://www.larisantos.com.br/blog/${route.params.slug}`,
    },
  ],
})

Sem o canonical apontando para si mesma, uma página de listagem pode acabar canonizada para a home, e o Google deixa de indexá-la como página própria. É um erro sutil e comum, que vale conferir em cada rota.


og:image dinâmica: um preview por página

A og:image é a imagem exibida quando um link é compartilhado no WhatsApp, no LinkedIn ou no Twitter. Por muito tempo era uma imagem fixa, a mesma para o site inteiro. Isso já resolve para uma landing page única, mas cai por terra assim que o site tem mais de uma página relevante: cada URL compartilhada exibe o mesmo cartão genérico, sem título e sem contexto do que está por trás daquele link. Num blog fica evidente: cada post merece um preview próprio. Mas o problema vale para qualquer conjunto de páginas dinâmicas.

O módulo @nuxtjs/seo resolve isso gerando a imagem no servidor a partir de um componente Vue. A convenção é o ponto elegante da abordagem: qualquer componente criado em components/OgImage/ vira automaticamente um template disponível. O nome do arquivo é o primeiro argumento do defineOgImageComponent, e as props do componente são os dados passados no segundo argumento.

As dimensões e o renderer padrão ficam globais no nuxt.config.ts, sob ogImage.defaults (1200x630, renderer satori), já que valem para todo o site. O que muda por rota é a chamada, feita no <script setup> de cada página:

ts
defineOgImageComponent("OgImageOgBlogPost", {
  title: post.value.title,
  description: post.value.description,
  date: formatDate(post.value.date),
  image: post.value.image,
  tags: post.value.tags,
})

Cada post passa seus próprios dados, e o componente OgBlogPost.vue monta a imagem 1200x630 com o título, a descrição e as tags daquele post. Nos bastidores, o Satori pega esse componente e renderiza como imagem no servidor. Todo link compartilhado passa a refletir o conteúdo real da página, sem geração manual de imagem.

O componente é um Vue normal, mas com uma restrição importante: o Satori só entende um subconjunto de CSS, e não roda JavaScript. Isso exclui classes utilitárias que dependam de runtime e qualquer layout que precise de cálculo no navegador. Tudo é estilo inline e flexbox direto: gradiente de fundo, título com line-clamp para não estourar, tags como pílulas e a marca do site num canto.

Um detalhe passa batido e não dá erro nenhum: o segundo argumento do defineOgImageComponent é um objeto, não um array. Se ele for passado como [] em vez de {}, a og:image simplesmente não é gerada, e o console fica em silêncio. Quando o preview não sair, o tipo desse argumento é o primeiro lugar para olhar.

Para validar antes de publicar, o metatags.io simula como o link vai aparecer em cada rede.


Schema.org: dado estruturado que a IA entende

Se a og:image fala com a pessoa que vê o link, o Schema.org fala com a máquina que lê a página. É um vocabulário de dados estruturados injetado no HTML em formato JSON-LD, descrevendo o que aquele conteúdo é: um artigo, uma pessoa, uma organização, uma trilha de navegação. O buscador usa isso para montar rich snippets, e as IAs usam para entender a entidade por trás do site com menos ambiguidade.

No Nuxt, o useSchemaOrg do @nuxtjs/seo traz funções prontas para os tipos mais comuns. O primeiro cuidado é escolher o tipo raiz certo. Um portfólio pessoal se descreve como Person, não como Organization, porque é Person que habilita o snippet com nome, cargo e foto. Como esse dado descreve a autora do site inteiro, ele é declarado uma vez só no app.vue, não em cada página:

ts
useSchemaOrg([
  definePerson({
    "@id": "https://www.larisantos.com.br/#larissa",
    name: "Larissa Santos",
    jobTitle: "Desenvolvedora Frontend",
    image: "https://www.larisantos.com.br/images/larissa.png",
    sameAs: [
      "https://github.com/LariMoro20",
      "https://www.linkedin.com/in/lari-moro-ss",
    ],
  }),
])

O sameAs é o campo que conecta os perfis. Ele informa ao buscador e às IAs que a pessoa deste site é a mesma do GitHub e do LinkedIn. É isso que consolida o reconhecimento de entidade, quando várias fontes apontam para a mesma pessoa com dados consistentes.

Já dentro da página de cada post, o tipo certo é BlogPosting, com título, datas de publicação e modificação, autor e palavras-chave:

ts
defineArticle({
  "@type": "BlogPosting",
  headline: post.value.title,
  description: post.value.description,
  image: toAbsoluteUrl(post.value.image),
  datePublished: post.value.date,
  dateModified: post.value.updated ?? post.value.date,
  author: {
    "@id": "https://www.larisantos.com.br/#larissa",
  },
  keywords: post.value.tags,
  inLanguage: "pt-BR",
})

O author aponta por @id para a mesma pessoa definida na raiz. Em vez de repetir os dados do autor dentro do artigo, só o identificador é referenciado. Assim o grafo de dados fica coerente: existe uma única entidade, e cada post é escrito por ela.

Junto do artigo vai um BreadcrumbList, que é a trilha Home > Blog > Título do Post:

ts
defineBreadcrumb({
  itemListElement: [
    { name: "Home", item: "https://www.larisantos.com.br" },
    { name: "Blog", item: "https://www.larisantos.com.br/blog" },
    {
      name: post.value.title,
      item: `https://www.larisantos.com.br/blog/${route.params.slug}`,
    },
  ],
})

Ele aparece nos resultados de busca como o caminho de navegação em vez da URL crua, e ajuda tanto o Google quanto a IA a entenderem onde a página se encaixa na estrutura do site.


AEO: transformar o conteúdo em resposta

Enquanto o BlogPosting diz o que a página é, o FAQPage diz o que ela responde. Essa é a ponte para o AEO. Quando um post tem uma seção de perguntas e respostas, dá para expor isso como dado estruturado, e o Google pode usar direto como resposta destacada.

A estrutura do FAQPage é uma lista de perguntas, cada uma com sua resposta aceita:

ts
useSchemaOrg([
  {
    "@type": "FAQPage",
    mainEntity: [
      {
        "@type": "Question",
        name: "Como gerar uma og:image dinâmica no Nuxt?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "Com o módulo @nuxtjs/seo. Cada componente em components/OgImage/ vira um template, e o defineOgImageComponent passa os dados da página.",
        },
      },
    ],
  },
])

Uma forma prática é manter essas perguntas no próprio frontmatter do post, num campo faq, e só montar o schema quando o post tiver perguntas, para não marcar página com dado estruturado vazio. O que mais pesa aqui é a redação da resposta. Ela precisa funcionar fora de contexto, responder a pergunta em uma ou duas frases sem depender do resto do texto, porque é assim que vai ser extraída e mostrada como resposta destacada.


GEO: falar com os bots de IA

A última frente é a que mais mudou nos últimos tempos. As IAs generativas rastreiam a web com crawlers próprios, cada um com seu user-agent, e boa parte deles respeita o robots.txt para decidir se lê o conteúdo do site. Para aparecer nas respostas dessas IAs, é preciso liberar esses agentes de forma explícita.

No Nuxt, em vez de escrever o arquivo na mão, os grupos são declarados no nuxt.config.ts sob robots, e o módulo gera o robots.txt. O arquivo resultante lista os bots que interessam:

txt
User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: Google-Extended
Allow: /

O Google-Extended é o que controla o uso do conteúdo pelas funcionalidades de IA do Google, separado do crawler de busca tradicional. Com ele dá para bloquear a IA e continuar aparecendo na busca normal, ou o contrário, e essa separação deixa a escolha nas mãos de quem mantém o site.

A segunda parte do GEO é o llms.txt, um arquivo na raiz do site, em /llms.txt, que descreve em linguagem natural o que o site é, quem o mantém e como o conteúdo está organizado. É um resumo curado, feito para modelo de linguagem ler. Não é um padrão consolidado ainda, mas é barato de manter e serve como um contexto direto sobre a entidade, sem a IA ter que inferir tudo só do HTML.

O formato é Markdown simples: um título com o nome, um resumo em blockquote e seções com os dados e os links principais.

md
# Larissa Santos, Desenvolvedora Frontend

> Portfólio e blog de Larissa Santos, desenvolvedora Frontend
> especialista em Vue.js, Nuxt e Quasar, com background fullstack em PHP.

## Sobre
- **Cargo:** Desenvolvedora Frontend
- **Especialidades:** Vue.js 3, Nuxt, TypeScript, Pinia, Tailwind CSS
- **Site:** https://www.larisantos.com.br

## Links
- [Portfólio](https://www.larisantos.com.br)
- [Blog](https://www.larisantos.com.br/blog)
- [GitHub](https://github.com/LariMoro20)

## Blog
Artigos técnicos em português sobre frontend, JavaScript, Vue.js e arquitetura.
- [Vuex vs Pinia: guia completo de gerenciamento de estado](https://www.larisantos.com.br/blog/vuex-vs-pinia)
- [Virtual DOM por baixo dos panos: um mini-framework do zero](https://www.larisantos.com.br/blog/virtual-dom-mini-framework-do-zero)

O conteúdo é enxuto e factual: nome, o que o site é, o posicionamento, a lista de seções e uma linha sobre cada área. Nada de invenção, só a mesma informação que já está no site, organizada de forma que uma IA consuma rápido.


sitemap.xml: o mapa para os crawlers

Se o robots.txt diz quem pode entrar, o sitemap.xml diz o que existe para ser rastreado. É a lista de todas as URLs do site, que o buscador usa para descobrir páginas sem depender só de seguir links. Para um blog que cresce, é o que garante que cada post novo seja encontrado.

No Nuxt, o módulo @nuxtjs/seo já traz o @nuxtjs/sitemap, e no caso mais simples a configuração é literalmente vazia:

ts
export default defineNuxtConfig({
  sitemap: {},
})

Com isso, as rotas estáticas e as que estão no prerender já entram no sitemap automaticamente. O ponto de atenção são as rotas dinâmicas, como /blog/[slug], que não existem como arquivo e por isso não são descobertas sozinhas. A solução é fornecer essas URLs por um endpoint que o sitemap consome:

ts
// server/api/__sitemap__/urls.ts
export default defineSitemapEventHandler(async () => {
  const posts = await queryCollection('blog').all()

  return posts.map((post) => ({
    loc: `/blog/${post.slug}`,
    lastmod: post.updated ?? post.date,
  }))
})

O endpoint busca os posts na mesma fonte que alimenta o blog, seja o @nuxt/content, um CMS headless ou uma API, e devolve as URLs com a data de última modificação. Assim o sitemap fica sempre em dia com o conteúdo real, sem lista manual para manter.

Juntando tudo, a regra de onde cada coisa mora é: o que vale para o site inteiro fica no nuxt.config.ts ou no app.vue, e o que muda de página para página fica na página.

ConfiguraçãoOnde mora
site (url, name, description, locale)nuxt.config.ts
robots (bots de IA) + sitemapnuxt.config.ts
ogImage.defaults (1200x630, satori)nuxt.config.ts
Schema Person do autorapp.vue
useSeoMeta + canonicalpágina
defineOgImageComponentpágina
BlogPosting / BreadcrumbList / FAQPagepágina
Endpoint do sitemap dinâmicoserver/api/__sitemap__/urls.ts

SEO hoje é escrever para três leitores

A página não tem mais um público só. A mesma URL é lida pelo crawler do Google, pelo mecanismo que monta a resposta direta e pela IA que vai citá-la. A og:image cuida da pessoa que vê o link, o Schema.org cuida da máquina que interpreta o conteúdo, o FAQPage transforma parte do texto em resposta pronta e o robots.txt mais o llms.txt decidem o que as IAs enxergam.

Algumas frentes valem como próximo passo. Domínio próprio pesa no E-E-A-T e no reconhecimento de entidade, um site em subdomínio de plataforma nunca soa tão sólido quanto um domínio dedicado. E o FAQPage pode ir além dos posts, cobrindo dúvidas de serviço e de produto em qualquer página.

Nenhuma dessas três frentes é complicada isoladamente. O que muda é a mentalidade: parar de otimizar a página para um leitor e escrever para todos eles ao mesmo tempo, sabendo que cada um lê de um jeito.


Onde testar

Nada disso vale sem validação, e dá para conferir tudo com ferramenta gratuita:

  • opengraph.xyz simula como a og:image e as meta tags aparecem no Facebook, LinkedIn, Twitter e outros, antes de compartilhar o link de verdade.
  • PageSpeed Insights analisa performance, Core Web Vitals e SEO técnico, com um relatório separado para mobile e desktop, e já traz também uma verificação de GEO.
  • Google Search Console mostra como o Google enxerga o site: indexação, cobertura, erros de rastreamento e validação dos dados estruturados que aparecem como rich results.
  • Diagnóstico de SEO e GEO da Automarticles faz uma varredura das duas frentes juntas, incluindo os pontos de GEO que a maioria das ferramentas clássicas de SEO ainda não cobre.

Leituras relacionadas

Perguntas frequentes

Qual a diferença entre SEO, GEO e AEO?

SEO é a otimização clássica para buscadores como o Google indexarem e ranquearem a página. AEO (Answer Engine Optimization) prepara o conteúdo para virar resposta direta, aquele bloco que aparece no topo da busca ou no AI Overview. GEO (Generative Engine Optimization) é a visibilidade dentro de respostas de IAs generativas como ChatGPT e Perplexity, que citam fontes ao responder. As três frentes se sobrepõem, mas cada uma resolve um tipo de leitura diferente da mesma página.

Como gerar uma og:image dinâmica por página no Nuxt?

Com o módulo @nuxtjs/seo. Qualquer componente Vue criado em components/OgImage/ vira um template disponível, e o defineOgImageComponent na página escolhe o template e passa os dados. O Satori renderiza esse componente como imagem 1200x630 no servidor, então cada post gera um preview próprio com título, descrição e tags reais sem configuração manual.

O robots.txt precisa liberar os bots de IA separadamente?

Sim. Crawlers de IA usam user-agents próprios, como GPTBot, ClaudeBot, PerplexityBot e Google-Extended, e alguns respeitam o robots.txt para decidir se leem seu conteúdo. Se você quer aparecer em respostas de IA, precisa liberar esses agentes explicitamente, porque bloquear ou omitir pode deixar seu site de fora do treinamento e da citação.

O que é o llms.txt e onde ele fica?

É um arquivo de texto na raiz do site, em /llms.txt, que descreve em linguagem natural o que o site é, quem o mantém e como o conteúdo está organizado. Serve para orientar modelos de linguagem sobre o contexto do site, como um resumo curado para IA. Não é um padrão obrigatório ainda, mas é barato de manter e ajuda no reconhecimento de entidade.

LarissaSantos

Desenvolvedora Frontend apaixonada por criar experiências digitais incríveis

Navegação

2026 © Larissa Santos Feito com Vue.js