Next.js 15: “55 milhões de páginas” sem SSG, SSR sob demanda e SEO
No CNPJ Aberto eu tenho a rota /cnpj/[cnpj] com title, description, OG image e JSON-LD por empresa. Pra mim não dava pra pré-gerar tudo no build: seriam dias e armazenamento absurdo.
A saída que eu escolhi foi App Router + Server Components: HTML no servidor na hora do request, metadados com generateMetadata, sem promessa de SSG pra cada CNPJ.
Server Component na página
Minha page.tsx da ficha não é "use client". O que ganhei:
- HTML útil no primeiro byte pra crawler
generateMetadataalinhado com o mesmo dado da página- Menos JS obrigatório no primeiro carregamento
O padrão que ajudou: cache() do React em volta do fetch da empresa — generateMetadata e o corpo da página compartilham a mesma chamada e eu evito query duplicada.
OG image dinâmica
Usei opengraph-image.tsx com ImageResponse (next/og): gero imagem sob demanda com nome da empresa, CNPJ, situação, etc. Quando alguém compartilha o link, eu quero preview real, não um card genérico.
JS só onde precisa
A ficha tem coisas pesadas (grafo, mapa, ações Pro…). Eu usei next/dynamic e lazy() onde o interativo não é above-the-fold — chunk separado, carrega quando faz sentido. Eu vi menos JS inicial e LCP mais previsível.
URL canônica
Eu sei que CNPJ aparece formatado de mil jeitos na URL. Por isso eu coloquei middleware que redireciona 301 pra forma “só dígitos” quando detecta 14 dígitos com lixo de pontuação — eu quis evitar diluir sinal de SEO em duplicata.
JSON-LD
Eu injeto Organization com endereço, taxID, etc., como <script type="application/ld+json">. Eu não trato isso como mágica de ranking, mas deixa explícita a intenção da página pra quem consome structured data.
API no mesmo host
Eu configurei rewrite no next.config: o browser chama /api/... e o Next encaminha pro backend. Eu reduzi dor de CORS no cliente; no SSR eu falo com URL interna direto no backend.
Métricas (referência)
PageSpeed / campo real variam, mas a direção que eu vi foi: LCP na casa de ~1 s, CLS baixo, bundle inicial da ficha na casa de dezenas de KB gzip depois do split — não centenas.
Pra “infinitas” URLs dinâmicas, SSR + metadados server-side + dynamic import foi o que mais fez sentido pro site, em vez de fingir que dá pra estátizar o Brasil inteiro no next build.