Capítulo 15 de 456

Metadata and OG images

Core Idea

As Metadata APIs definem metadados de aplicação para SEO e compartilhamento web: objeto estático metadata, função dinâmica generateMetadata, e convenções de arquivo para favicons e imagens OG. Exports de metadata só funcionam em Server Components.

Key Concepts

  • Meta tags padrão: charset e viewport são sempre adicionadas mesmo sem metadata definida.
  • metadata (objeto estático): exportado de layout.js/page.js estático, para metadados que não dependem de dados.
  • generateMetadata: função async que busca dados (ex.: fetch) para gerar metadata dinâmica; recebe params, searchParams e parent (ResolvingMetadata).
  • Streaming metadata: em páginas renderizadas dinamicamente, o metadata é injetado no HTML separadamente assim que generateMetadata resolve, sem bloquear a UI; desabilitado automaticamente para bots que esperam metadata no <head> (Twitterbot, Slackbot, Bingbot), detectados via User-Agent. Controlável via htmlLimitedBots. Páginas prerenderizadas não usam streaming (metadata já resolvida no build).
  • Memoização com cache do React: evita fetch duplicado quando o mesmo dado é necessário tanto em generateMetadata quanto na página.
  • Favicons: favicon.ico na raiz de app/ (ou geração programática).
  • Static Open Graph images: opengraph-image.jpg na raiz ou em subpastas de rota; a imagem mais específica na árvore de pastas tem precedência.
  • ImageResponse (next/og): gera imagens OG dinamicamente via JSX/CSS (flexbox e subset de CSS; não suporta display: grid).

Code Examples

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My Blog',
  description: '...',
}

export default function Layout() {}
  • O que demonstra: metadata estática exportada de um layout.
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const slug = (await params).slug
  const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) => res.json())
  return { title: post.title, description: post.description }
}
  • O que demonstra: metadata dinâmica dependente de dado buscado por slug.
import { cache } from 'react'
import { db } from '@/app/lib/db'

// getPost será usada duas vezes, mas executa só uma
export const getPost = cache(async (slug: string) => {
  return await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
})
  • O que demonstra: memoizar a mesma query de dados usada tanto em generateMetadata quanto no corpo da página, evitando fetch duplicado.
import { ImageResponse } from 'next/og'
import { getPost } from '@/app/lib/data'

export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'

export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = await getPost(slug)
  return new ImageResponse(
    <div style={{ fontSize: 128, background: 'white', width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      {post.title}
    </div>
  )
}
  • O que demonstra: gerar OG image dinâmica por rota (app/blog/[slug]/opengraph-image.tsx) a partir de dado da própria rota.

Reference Tables

Arquivo de convençãoPropósito
favicon.ico, apple-icon.jpg, icon.jpgÍcones de site
opengraph-image.jpg, twitter-image.jpgImagens de compartilhamento social
robots.txtDiretivas para crawlers
sitemap.xmlMapa do site

Anti-patterns

  • Buscar o mesmo dado separadamente em generateMetadata e na página: duplica a requisição; usar cache do React para memoizar.
  • Usar display: grid em ImageResponse: não suportado (satori só cobre flexbox e subset de CSS); reestruturar o layout com flexbox.
  • Esperar metadata em streaming no <head> para bots de preview social: streaming é desabilitado automaticamente para eles, mas é importante saber que o comportamento difere de usuários reais.

Key Takeaways

  1. metadata estático é para o que não depende de dado; generateMetadata é para o que depende (ex.: dado de post buscado por slug).
  2. Streaming metadata melhora performance percebida para usuários, mas é desativado para bots que exigem <head> completo (configurável via htmlLimitedBots).
  3. Memoizar fetch compartilhado entre generateMetadata e a página com cache do React evita requisição duplicada.
  4. A imagem OG mais específica na hierarquia de pastas vence sobre uma mais genérica acima dela.
  5. ImageResponse roda sobre @vercel/og/satori/resvg: suporta só flexbox e subset de CSS, não grid.

Connects To

  • Caching (ch009): dentro de generateMetadata e generateViewport, fetches não cacheados ou acesso a dado de runtime disparam os mesmos insights de Cache Components.
  • Route Handlers (ch016): arquivos especiais de metadata (sitemap, opengraph-image, icon) seguem o mesmo modelo estático-por-padrão descrito lá.