Virtus Design
Voltar ao Blog
Desenvolvimento

SEO programático em Next.js 16: metadata, sitemap dinâmico e schema.org

generateMetadata dinâmica, sitemap.ts lendo a REST API, JSON-LD BlogPosting e FAQPage automáticos. SEO técnico no Next.js 16, sem plugins.

SEO programático em Next.js 16: metadata, sitemap dinâmico e schema.org

TL;DR

SEO técnico em Next.js 16 não exige plugins. O App Router entrega generateMetadata dinâmica, sitemap.ts dinâmico, robots.ts, OG image gerada em runtime e JSON-LD via componente, tudo nativo. A combinação certa, alimentada pela REST API do WordPress, entrega rich results na SERP, sitemap sempre atualizado e metadata por página, sem que o time editorial precise saber o que é schema.org. Este artigo mostra a implementação completa, com tipos TypeScript e exemplos reais.

generateMetadata: o que sai automático

Toda página do App Router pode exportar uma função assíncrona generateMetadata que retorna o objeto Metadata. O Next.js usa esse objeto para gerar todas as tags HTML necessárias: title, description, canonical, Open Graph, Twitter Card, robots.

A função recebe os params da rota e pode buscar dados externos (incluindo a REST API do WordPress) antes de retornar a metadata. Em uma página de post, isso significa metadata personalizada por slug, sem código duplicado:

export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
const { slug } = await params;
const post = await getPostBySlug(slug);
if (!post) return {};
return {
title: stripHtml(post.title.rendered),
description: stripHtml(post.excerpt.rendered).slice(0, 160),
openGraph: {
type: "article",
images: [{ url: getFeaturedImageUrl(post), width: 1200, height: 630 }],
},
alternates: { canonical: `/blog/${slug}` },
};
}

Essa função roda no servidor, durante a geração da página. Como ISR está ativo, ela é executada uma vez a cada 5 minutos por slug, com o resultado cacheado no edge. Performance excelente.

JSON-LD em todo post: BlogPosting + autor + imagem

O Google entende uma página como sendo de blog quando encontra um JSON-LD BlogPosting no head. Esse schema desbloqueia exibição de data, autor e imagem nos resultados de busca, com aparência de “rich result” mais rica que o snippet padrão.

Em sites do nosso portfólio, o JSON-LD é emitido por um componente reutilizável que recebe o post e gera o schema completo:

function BlogPostingSchema({ post }: { post: WPPost }) {
const schema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: stripHtml(post.title.rendered),
image: getFullImageUrl(post),
datePublished: post.date,
dateModified: post.modified,
author: {
"@type": "Organization",
name: "Virtus Design",
url: "https://virtusdesign.com.br",
},
};
return <script type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} />;
}

Em projetos com autoria nominal por post, o author é populado dinamicamente a partir do _embedded.author da REST API. Em sites institucionais, costuma fazer mais sentido manter o autor como organização.

FAQPage automático a partir do bloco Yoast

Como detalhamos no artigo sobre renderização de blocos Gutenberg, blocos Yoast FAQ são extraíveis do content.rendered via regex. O passo final é transformar essas FAQs em JSON-LD FAQPage, que o Google reconhece como rich result expandível.

O componente faz exatamente o mesmo padrão do BlogPosting, só que condicional. Se o post não tem FAQs, o schema não é emitido:

function FaqSchema({ faqs }: { faqs: Array<{ question: string; answer: string }> }) {
if (faqs.length === 0) return null;
const schema = {
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqs.map(faq => ({
"@type": "Question",
name: faq.question,
acceptedAnswer: { "@type": "Answer", text: faq.answer },
})),
};
return <script type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} />;
}

Resultado: o time editorial adiciona um bloco FAQ no post, e o site emite automaticamente o schema correto. Nenhuma decisão técnica passa pelo editor.

Breadcrumbs são úteis para o usuário e para o Google. No HTML eles aparecem como uma trilha de navegação no topo da página (“Blog > Categoria > Post”). No JSON-LD, viram BreadcrumbList, que o Google usa para mostrar a estrutura do site na SERP no lugar da URL bruta.

O componente recebe a lista de items e emite o schema:

function BreadcrumbSchema({ items }: { items: Array<{ name: string; url: string }> }) {
const schema = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: items.map((item, i) => ({
"@type": "ListItem",
position: i + 1,
name: item.name,
item: item.url,
})),
};
return <script type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} />;
}

Em sites do nosso portfólio, BreadcrumbList aparece em todas as páginas de detalhe (post, página de categoria, página de produto) e nas listagens principais.

sitemap.ts dinâmico lendo a REST API

O Next.js 16 oferece um arquivo especial app/sitemap.ts que exporta uma função geradora de sitemap. Ela roda no servidor, pode buscar dados externos (a REST API do WordPress) e retorna a lista completa de URLs do site.

O resultado é exposto automaticamente em /sitemap.xml. O Next.js gera o XML válido a partir do array retornado:

// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPosts(1, 100);
const categories = await getCategories();
const baseUrl = "https://virtusdesign.com.br";
return [
{ url: baseUrl, lastModified: new Date(), changeFrequency: "weekly" },
{ url: `${baseUrl}/blog`, lastModified: new Date(), changeFrequency: "daily" },
...posts.map(p => ({
url: `${baseUrl}/blog/${p.slug}`,
lastModified: p.modified,
changeFrequency: "monthly" as const,
})),
...categories.map(c => ({
url: `${baseUrl}/blog/categoria/${c.slug}`,
changeFrequency: "weekly" as const,
})),
];
}

O sitemap revalida junto com o ISR. Cada 5 minutos (ou o intervalo configurado), o Next.js refaz a chamada à REST API e regenera. Posts novos aparecem no sitemap automaticamente, sem deploy.

Erro mais comum: esquecer de submeter o sitemap no Google Search Console. Mesmo com sitemap.xml acessível, ele só é lido sistematicamente quando submetido. Submissão é uma vez só, depois o Google atualiza por conta própria.

OG image: o detalhe que muda o CTR

Open Graph image é a imagem que aparece quando o link é compartilhado em redes sociais (LinkedIn, X, Facebook, WhatsApp). É também o que aparece em alguns rich results do Google. Uma OG image bem feita aumenta significativamente o CTR (taxa de clique).

O Next.js 16 oferece duas opções:

Imagem estática (mais simples): usar a imagem destacada do post como OG image. É o padrão para a maioria dos sites institucionais. Funciona bem se as imagens destacadas seguem um padrão visual consistente.

OG image dinâmica (mais sofisticada): gerar uma imagem em runtime com título do post, autor e branding. O Next.js permite isso via opengraph-image.tsx com a API ImageResponse. Útil em blogs com volume alto e padrão visual definido.

Em sites do nosso portfólio, começamos sempre com imagem estática (a destacada do post) e migramos para dinâmica quando o volume justifica o investimento de design.

Search Console: o que monitorar

Implementar SEO programático sem monitoramento é otimizar no escuro. O Google Search Console é a ferramenta gratuita que mostra como o Google enxerga o site:

Checklist: o que checar semanalmente

  • Cobertura: quantas páginas estão indexadas vs submetidas no sitemap. Discrepância grande indica problemas de qualidade ou erros de canonical

  • Performance: cliques, impressões, CTR e posição média por página e por keyword. Tendências de queda exigem investigação imediata

  • Core Web Vitals: dados CrUX em tempo quase real, segmentados por dispositivo e por URL

  • Rich Results: validação de schema.org. Se o seu BlogPosting ou FAQPage tem erro, aparece aqui antes de afetar tráfego

  • Sitemaps: verificar que o sitemap.xml foi lido recentemente e quantas URLs foram processadas

Configurar alertas no Search Console (via integração com email) avisa quando há queda brusca de tráfego ou erros novos de cobertura.

robots.ts: o detalhe esquecido

O Next.js 16 também tem um arquivo app/robots.ts que exporta o robots.txt do site. Em projetos institucionais, o conteúdo é simples mas precisa estar correto:

// app/robots.ts
export default function robots(): MetadataRoute.Robots {
return {
rules: [{ userAgent: "*", allow: "/", disallow: ["/api/", "/admin/"] }],
sitemap: "https://virtusdesign.com.br/sitemap.xml",
};
}

O ponto crítico é apontar o sitemap. Sem isso, alguns crawlers não encontram a lista completa de URLs e indexam o site parcialmente.

Próximos passos

SEO programático no Next.js 16 entrega um site institucional totalmente otimizado, sem plugins, sem dependências externas e sem que a equipe editorial precise se preocupar com schema.org. É a última peça de uma série que começou na visão estratégica, passou pela arquitetura técnica, pela renderização de blocos Gutenberg e pela otimização de Core Web Vitals.

Aplicar este padrão no seu site requer atenção a detalhes mas não exige tooling complexo. O Next.js 16 entrega tudo nativamente; o que falta é o método para combinar as peças com consistência. Se você quer implementar este modelo no seu projeto, fale com a Virtus Design.

Conheça também nossa ferramenta gratuita de auditoria SEO, que avalia 9 categorias de SEO técnico em 30 segundos, e o portfólio completo de serviços em desenvolvimento, design e SEO técnico.

Perguntas Frequentes

Posso usar Yoast SEO no WordPress mesmo com headless?
Sim, e faz sentido. O Yoast continua útil no painel para campos editoriais (focus keyword, meta description manual, snippet preview). O front-end Next.js usa esses dados via REST API quando disponíveis e gera o que falta automaticamente. É o melhor dos dois mundos.
Schema.org vale a pena se meu site é pequeno?
Sim. O Google usa schema.org como sinal de qualidade técnica e também como facilitador de rich results. Sites pequenos com schema.org bem implementado ranqueiam acima de sites maiores sem schema. O custo de implementar é baixo e o ganho é imediato.
O sitemap.xml dinâmico funciona com ISR?
Sim. O Next.js gera o sitemap durante a geração da página e cacheia junto com o resto do site. Cada revalidação da REST API atualiza o sitemap automaticamente. Posts novos aparecem em até 5 minutos, sem deploy.
Vale a pena gerar OG image dinâmica desde o início?
Não necessariamente. OG image dinâmica exige investimento de design e implementação. Em sites institucionais com edição moderada, a imagem destacada do post como OG image entrega resultado equivalente com complexidade menor. Migrar para OG dinâmica faz sentido quando o volume editorial justifica.
Como sei se meu schema.org está correto?
O Google oferece o Rich Results Test (search.google.com/test/rich-results) que valida qualquer URL pública e mostra os schemas encontrados, com erros e avisos detalhados. Vale rodar nas páginas principais após implementação e periodicamente para detectar regressões.
V

Virtus Design

Transformamos negócios em marcas digitais extraordinárias com estratégia, design e tecnologia.

Artigos relacionados