Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Use as convenções do Next.js App Router para configurar favicons, gerar imagens dinâmicas do Open Graph e configurar metadados de compartilhamento social. O Next.js suporta abordagens baseadas em arquivos e em configuração.
Configuração de Favicon (baseada em arquivo):
Coloque os arquivos de ícone no diretório app/ com nomes convencionais:
app/
favicon.ico # Ícone da aba do navegador (32x32 ou 16x16)
icon.png # Navegadores modernos (32x32)
icon.svg # Ícone escalável para navegadores modernos
apple-icon.png # Ícone de toque da Apple (180x180)
O Next.js gera automaticamente as tags <link> corretas em <head> para esses arquivos.
Imagem OG estática (baseada em arquivo):
app/
opengraph-image.png # Imagem OG padrão (1200x630)
twitter-image.png # Imagem do cartão do Twitter (1200x630)
Abordagem de configuração de metadados:
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
title: {
default: "Meu App",
template: "%s | Meu App",
},
description: "Uma descrição do meu app",
icons: {
icon: [
{ url: "/favicon.ico", sizes: "32x32" },
{ url: "/icon.svg", type: "image/svg+xml" },
],
apple: [{ url: "/apple-icon.png", sizes: "180x180" }],
},
openGraph: {
title: "Meu App",
description: "Uma descrição do meu app",
url: "https://myapp.com",
siteName: "Meu App",
images: [
{
url: "/og-image.png",
width: 1200,
height: 630,
alt: "Prévia do Meu App",
},
],
locale: "pt_BR",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Meu App",
description: "Uma descrição do meu app",
images: ["/twitter-image.png"],
},
};Um gerador dinâmico de imagens OG para posts de blog usando ImageResponse:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from "next/og";
export const runtime = "edge";
export const alt = "Prévia do post do blog";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
interface Props {
params: Promise<{ slug: string }>;
}
async function getPost(slug: string) {
// Substitua pela sua lógica de busca de dados
const posts: Record<string, { title: string; author: string; category: string; date: string }> = {
"getting-started": {
title: "Começando com Next.js",
author: "Jane Smith",
category: "Tutorial",
date: "2026-03-15",
},
"server-components": {
title: "Entendendo os Componentes de Servidor React",
author: "John Doe",
category: "Aprofundado",
date: "2026-03-20",
},
};
return posts[slug] || { title: "Post do Blog", author: "Autor", category: "Geral", date: "2026-01-01" };
}
export default async function OGImage({ params }: Props) {
const { slug } = await params;
const post = await getPost(slug);
return new ImageResponse(
(
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
padding: "60px",
background: "linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%)",
color: "white",
fontFamily: "system-ui, sans-serif",
}}
>
{/* Selo de Categoria */}
<div
style={{
display: "flex",
alignItems: "center",
gap: "12px",
}}
>
<div
style={{
backgroundColor: "#3b82f6",
borderRadius: "9999px",
padding: "6px 16px",
fontSize: "16px",
fontWeight: 600,
}}
>
{post.category}
</div>
</div>
{/* Título */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
}}
>
<div
style={{
fontSize: "56px",
fontWeight: 800,
lineHeight: 1.1,
letterSpacing: "-0.02em",
maxWidth: "900px",
}}
>
{post.title}
</div>
</div>
{/* Rodapé */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
fontSize: "20px",
color: "#94a3b8",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<div
style={{
width: "40px",
height: "40px",
borderRadius: "50%",
backgroundColor: "#3b82f6",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "white",
fontSize: "18px",
fontWeight: 700,
}}
>
{post.author[0]}
</div>
<span>{post.author}</span>
</div>
<span>{post.date}</span>
</div>
</div>
),
{
...size,
}
);
}// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
interface PageProps {
params: Promise<{ slug: string }>;
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
// A imagem OG é descoberta automaticamente a partir de opengraph-image.tsx
// mas você pode adicionar metadados adicionais aqui
return {
title: `Blog: ${slug}`,
description: `Leia sobre ${slug}`,
};
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params;
return (
<article className="prose mx-auto max-w-3xl px-4 py-8">
<h1>{slug}</h1>
<p>Conteúdo do post do blog aqui...</p>
</article>
);
}Favicon dinâmico com icon.tsx:
// app/icon.tsx
import { ImageResponse } from "next/og";
export const size = { width: 32, height: 32 };
export const contentType = "image/png";
export default function Icon() {
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "#0f172a",
borderRadius: "6px",
color: "white",
fontSize: "20px",
fontWeight: 800,
}}
>
A
</div>
),
{ ...size }
);
}favicon.ico, icon.png, apple-icon.png, opengraph-image.png e twitter-image.png em segmentos de rota são automaticamente reconhecidos.ImageResponse de next/og, que renderiza JSX em uma imagem usando Satori (uma biblioteca que converte HTML/CSS em SVG) e depois converte para PNG.opengraph-image.tsx podem ser colocados em qualquer nível de segmento de rota. Um arquivo em app/blog/[slug]/opengraph-image.tsx gera imagens OG únicas por post de blog.runtime = "edge" garante que a imagem seja gerada na edge para tempos de resposta rápidos. Você também pode usar runtime = "nodejs" se precisar de APIs do Node.js.generateMetadata e metadados baseados em arquivos podem coexistir. Imagens baseadas em arquivos têm precedência para seus campos de metadados específicos.Fontes personalizadas em imagens OG:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from "next/og";
export const runtime = "edge";
export default async function OGImage() {
const fontData = await fetch(
new URL("../../../public/fonts/Inter-Bold.ttf", import.meta.url)
).then((res) => res.arrayBuffer());
return new ImageResponse(
(
<div style={{ fontFamily: "Inter", fontSize: 48, fontWeight: 700 }}>
Título com Fonte Personalizada
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: "Inter",
data: fontData,
style: "normal",
weight: 700,
},
],
}
);
}Ícones do manifesto do aplicativo web:
// app/manifest.ts
import type { MetadataRoute } from "next";
export default function manifest(): MetadataRoute.Manifest {
return {
name: "Meu App",
short_name: "App",
start_url: "/",
display: "standalone",
background_color: "#ffffff",
theme_color: "#0f172a",
icons: [
{ src: "/icon-192.png", sizes: "192x192", type: "image/png" },
{ src: "/icon-512.png", sizes: "512x512", type: "image/png" },
{ src: "/icon-512.png", sizes: "512x512", type: "image/png", purpose: "maskable" },
],
};
}ImageResponse é importado de next/og. Ele aceita JSX como seu primeiro argumento e um objeto de opções como o segundo.Metadata de next fornece tipagem completa para todos os campos de metadados, incluindo openGraph, twitter e icons.Promise<Metadata>.import type { Metadata, ResolvingMetadata } from "next";
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> },
parent: ResolvingMetadata
): Promise<Metadata> {
const { slug } = await params;
const previousImages = (await parent).openGraph?.images || [];
return {
openGraph: {
images: [`/api/og?title=${slug}`, ...previousImages],
},
};
}ImageResponse não é React completo. Ele suporta um subconjunto de CSS (apenas layout flexbox, sem grid, opções limitadas para position: absolute). Verifique a documentação do Satori para propriedades CSS suportadas.ArrayBuffer. Você não pode usar @font-face do CSS ou URLs do Google Fonts diretamente em ImageResponse.summary_large_image.favicon.ico deve estar no diretório raiz app/. Colocá-lo em um subdiretório não funcionará.background-image com url() não é suportado. Use a tag img no JSX em vez disso.alt em opengraph-image.tsx é necessária para acessibilidade. Ela se torna a meta tag og:image:alt.| Abordagem | Prós | Contras |
|---|---|---|
| Baseada em arquivo (opengraph-image.tsx) | Descoberta automática, imagens por rota, type-safe | Limitações de CSS do Satori |
| Rota de API (/api/og) | Controle total, reutilizável entre rotas | Ligação manual de metadados |
| Imagens estáticas | Mais simples, sem custo de computação | Mesma imagem para todas as páginas |
| Cloudinary OG | Transformações avançadas, sobreposições de texto | Serviço externo, custo |
| @vercel/og (standalone) | Funciona fora do Next.js | Requer configuração separada |
favicon.ico -- ícone da aba do navegador.icon.png / icon.svg -- ícones de navegadores modernos.apple-icon.png -- ícone de toque da Apple (180x180).opengraph-image.png -- imagem OG padrão (1200x630).twitter-image.png -- imagem do cartão do Twitter (1200x630).summary_large_image do Twitter também usam 1200x630.background-image com url() não é suportado; use uma tag img em vez disso.position: absolute são limitadas.const fontData = await fetch(
new URL("../../../public/fonts/Inter-Bold.ttf", import.meta.url)
).then((res) => res.arrayBuffer());
return new ImageResponse(<div style={{ fontFamily: "Inter" }}>Título</div>, {
fonts: [{ name: "Inter", data: fontData, weight: 700 }],
});Fontes devem ser carregadas como ArrayBuffer. @font-face do CSS e URLs do Google Fonts não funcionam.
app/.<link> correta para ele.// app/icon.tsx
import { ImageResponse } from "next/og";
export const size = { width: 32, height: 32 };
export const contentType = "image/png";
export default function Icon() {
return new ImageResponse(
<div style={{ background: "#0f172a", color: "white", fontSize: "20px" }}>A</div>,
{ ...size }
);
}import type { Metadata, ResolvingMetadata } from "next";Metadata fornece tipagem completa para os campos openGraph, twitter e icons.generateMetadata deve retornar Promise<Metadata>.ResolvingMetadata permite acessar metadados pai para mesclar imagens.og:image:alt para acessibilidade.opengraph-image.tsx) têm precedência para seus campos de metadados específicos.generateMetadata lida com outros campos como título e descrição.// app/manifest.ts
import type { MetadataRoute } from "next";
export default function manifest(): MetadataRoute.Manifest {
return {
name: "Meu App",
icons: [
{ src: "/icon-192.png", sizes: "192x192", type: "image/png" },
{ src: "/icon-512.png", sizes: "512x512", type: "image/png" },
],
};
}Revisado por Chris St. John·Última atualização: 7 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥