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

SEO sozinho não faz mais seu site aparecer. 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 Schema.org, FAQPage, robots.txt, llms.txt, sitemap e, por fim, a og:image dinâmica.


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. Antes de ir pro nível da página, vale definir os padrões que valem pro site inteiro direto no nuxt.config.ts, sob app.head. É ali que ficam o title de fallback, o titleTemplate que emenda o nome do site em cada página, o lang do documento e o favicon, tudo que não muda de rota pra rota:

ts
// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      title: "Larissa Santos, Desenvolvedora Frontend", // title de fallback
      titleTemplate: "%s | Larissa Santos",
      htmlAttrs: {
        lang: "pt-BR",
      },
      link: [
        { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
      ],
    },
  },
})

Decidir o que é global e o que é por página é uma escolha de arquitetura: o que descreve o site inteiro fica no config, o que descreve a rota fica na rota. Um cuidado antes de seguir: o definePageMeta, macro que também vive no <script setup>, não é ferramenta de SEO. Ele configura metadados da rota para o próprio Nuxt, como o layout e o middleware daquela página:

ts
// pages/mypage.vue
<script setup lang="ts">
definePageMeta({
  layout: "default",
})
</script>

É fácil confundir os dois pelo nome parecido, mas quem cuida das tags que o buscador e a IA leem é o useSeoMeta. Chamado dentro do <script setup>, ele trata title, description e as tags de Open Graph e Twitter de forma tipada, alimentado pelos dados reais da rota:

ts
// pages/blog/post.vue
<script setup lang="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,
})
</script>

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
// pages/blog/post.vue
<script setup lang="ts">
useHead({
  link: [
    {
      rel: "canonical",
      href: `https://www.larisantos.com.br/blog/${route.params.slug}`,
    },
  ],
})
</script>

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.


Schema.org: dado estruturado que a IA entende

O Schema.org fala direto 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
// pages/index.vue
<script setup lang="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",
    ],
  }),
])
</script>

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
// pages/blog/post.vue
<script setup lang="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",
})
</script>

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
// pages/blog/post.vue
<script setup lang="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}`,
    },
  ],
})
</script>

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
// pages/faq.vue
<script setup lang="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.",
        },
      },
    ],
  },
])
</script>

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: ser citado pelas IAs

A última frente é a que mais mudou nos últimos tempos, e também a mais fácil de entender errado. É comum reduzir o GEO a liberar os bots de IA no robots.txt, mas isso é só a porta de entrada. Aparecer citado numa resposta do ChatGPT ou do Perplexity depende antes de tudo do conteúdo, porque o modelo cita o que consegue extrair e confiar.

Na prática, o que a pesquisa de GEO aponta como mais determinante é bem concreto: citar fontes confiáveis, trazer dados e estatísticas, incluir trechos de especialistas e escrever com fluência, em frases diretas e blocos escaneáveis. É a mesma ideia do reconhecimento de entidade que o sameAs do Schema.org sustenta: um conteúdo específico, bem fundamentado e atribuído a uma pessoa real tem muito mais chance de ser usado como fonte do que um texto genérico. Boa parte do que constrói GEO, então, não é config, é redação.

Resolvido o conteúdo, vem a parte de acesso. 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 que esse conteúdo bem escrito chegue até elas, é 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
// robots.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.

Ainda na parte de acesso vem 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
// llmls.txt
# 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
// nuxt.config.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.


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

Deixei essa frente por último porque ela anda meio à parte das outras. SEO, AEO e GEO tratam de como o conteúdo é lido e citado, e a og:image cuida de outra coisa: como o link se apresenta quando é 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
// pages/blog/post.vue
<script setup lang="ts">
defineOgImageComponent("OgImageOgBlogPost", {
  title: post.value.title,
  description: post.value.description,
  date: formatDate(post.value.date),
  image: post.value.image,
  tags: post.value.tags,
})
</script>

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.


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

Fora do código: domínio e Google Search Console

Todo o esforço acima ajuda o Google a entender o site, mas ele ainda precisa saber que o site existe e que ele é seu. É aí que entra o Google Search Console. Você adiciona a propriedade do site, confirma que é o dono (com um registro DNS ou um arquivo de verificação) e passa a enxergar como o Google realmente lê o site: quais páginas estão indexadas, quais deram erro de rastreamento, como os dados estruturados aparecem como rich results e quais buscas trazem gente até você. Dá ainda para enviar o sitemap ali direto e pedir a indexação de uma URL nova, sem esperar o Google descobrir sozinho. Sem essa conta, você está otimizando às cegas, sem retorno nenhum sobre o que funcionou.

Antes disso, tem uma escolha que sustenta todo o reconhecimento de entidade: o domínio próprio. Um endereço como larisantos.com.br pesa no E-E-A-T (experiência, especialização, autoridade e confiança) de um jeito que um subdomínio de plataforma, tipo seunome.vercel.app, nunca alcança. É o domínio que amarra os sinais: o sameAs do Schema.org, o llms.txt, a verificação no Search Console e os perfis externos passam todos a apontar para o mesmo endereço estável. Sem ele, cada frente que a gente montou fica sem uma âncora comum.


SEO hoje é escrever para o Google e para as IAs

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 liberam o acesso das IAs, que só citam de verdade um conteúdo bem escrito e bem fundamentado.

Algumas frentes valem como próximo passo. O FAQPage pode ir além dos posts, cobrindo dúvidas de serviço e de produto em qualquer página, e o acompanhamento no Search Console vira rotina, guiando o que ajustar a partir do que o Google mostra sobre o site.

Nenhuma dessas três frentes é complicada isoladamente. O que muda é a mentalidade: parar de otimizar a página só para o buscador e passar a prepará-la para o Google, para a resposta direta e para as IAs ao mesmo tempo, sabendo que cada um consome a página 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 que faz um conteúdo ser citado por IAs como ChatGPT e Perplexity?

Antes de qualquer configuração, é o conteúdo. Os modelos citam o que conseguem extrair e confiar, então o que mais pesa é citar fontes confiáveis, trazer dados e estatísticas, incluir trechos de especialistas e escrever com fluência, em frases diretas e blocos escaneáveis. Somado a isso, o reconhecimento de entidade via Schema.org, com o sameAs conectando os perfis, mostra que existe uma pessoa ou marca real por trás. Liberar os bots de IA no robots.txt é só a porta de entrada, não a otimização em si.

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