Next.js
Open Graph images y generación dinámica
Explica imágenes Open Graph estáticas y dinámicas con ImageResponse, fuentes, datos, caché, fallbacks y límites de generación en Next.js.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Next.js
Explica imágenes Open Graph estáticas y dinámicas con ImageResponse, fuentes, datos, caché, fallbacks y límites de generación en Next.js.
Open Graph y Twitter images son una representación visual de una URL cuando se comparte. Next.js puede detectarlas como archivos estáticos o generarlas mediante ImageResponse. La imagen debe mantener identidad, legibilidad y caché sin depender de fuentes o datos que fallen en el momento de compartir.
app/opengraph-image.png
app/twitter-image.png
app/opengraph-image.alt.txt
app/portfolio/[slug]/opengraph-image.tsxUn archivo en un segmento aplica a sus descendientes hasta que otro lo reemplaza. Next.js genera las etiquetas correspondientes automáticamente.
Coloca un archivo cuando todas las URLs de la sección comparten diseño:
app/portfolio/opengraph-image.pngVentajas:
Limitación: no personaliza título o proyecto.
Los límites actuales de tamaño son 8 MB para Open Graph y 5 MB para Twitter image; un archivo mayor falla en build.
// app/portfolio/[slug]/opengraph-image.tsx
import { ImageResponse } from "next/og";
import { notFound } from "next/navigation";
export const alt = "Vista previa del proyecto";
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 project = await getPublishedProject(slug);
if (!project) notFound();
return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
padding: "72px",
background: "#09090b",
color: "white",
}}
>
<p style={{ fontSize: 28 }}>nicoo.dev · Proyecto</p>
<div>
<h1 style={{ fontSize: 72, margin: 0 }}>{project.title}</h1>
<p style={{ fontSize: 32 }}>{project.shortDescription}</p>
</div>
</div>
),
size,
);
}En Next.js 16, params llega como Promise. Si utilizas generateImageMetadata, el id recibido por la función de imagen también es una Promise.
share crawler requests page
→ reads og:image URL
→ requests generated image route
→ Next.js loads data/font/assets
→ ImageResponse renders image
→ social platform caches resultLa plataforma social puede mantener la imagen durante días aunque actualices la página. El diseño necesita versionado o herramientas de refresh del proveedor.
Utiliza una implementación basada en Satori y genera PNG. Soporta un subconjunto de CSS, principalmente Flexbox. No asumas compatibilidad completa con CSS del navegador.
Recomendaciones:
display: flex explícito.Puedes cargar una fuente local:
const inter = fetch(
new URL("../../../../assets/Inter-SemiBold.ttf", import.meta.url),
).then((response) => response.arrayBuffer());
export default async function Image({ params }: Props) {
const fontData = await inter;
return new ImageResponse(element, {
...size,
fonts: [
{
name: "Inter",
data: fontData,
style: "normal",
weight: 600,
},
],
});
}El loader se declara fuera para reutilización. WOFF/TTF/OTF son opciones; revisa licencia y peso. Un archivo demasiado grande aumenta cold start/bundle.
No compartas archivos de fuentes privados en respuestas al usuario final fuera de sus licencias.
ImageResponse no ejecuta next/image como una page normal. Usa URLs absolutas, data URLs o assets compatibles con el runtime.
Para un logo local, resuélvelo con new URL(..., import.meta.url) en un runtime soportado o utiliza un asset público absoluto.
No permitas que una URL externa controlada por usuario se convierta en un fetch arbitrario; puede crear SSRF. Usa hosts permitidos y timeouts.
El proyecto puede compartir el loader con generateMetadata y la page:
export const getPublishedProject = cache(async (slug: string) => {
return projectRepository.findPublishedBySlug(slug);
});Para reutilización entre requests:
async function getCachedProject(slug: string) {
"use cache";
cacheLife("hours");
cacheTag(`project:${slug}`);
return getProject(slug);
}Al publicar cambios, invalida la tag. Si necesitas que la imagen cambie inmediatamente en redes, también debes pedir un recrawl o cambiar su URL.
generateImageMetadata permite varias imágenes o variantes:
export async function generateImageMetadata({
params,
}: {
params: { slug: string };
}) {
return [
{ id: "default", size: { width: 1200, height: 630 }, contentType: "image/png" },
{ id: "square", size: { width: 1080, height: 1080 }, contentType: "image/png" },
];
}La función de imagen espera id:
export default async function Image({ params, id }: Props) {
const variant = await id;
// ...
}No generes múltiples imágenes si ninguna plataforma las necesita; aumenta build/runtime y mantenimiento.
Archivo estático:
opengraph-image.alt.txtO export alt en archivo dinámico.
Describe el contenido/propósito, no “imagen OG”. Aunque el soporte de plataformas varía, es parte del contrato accesible.
Una preview debe ser legible a tamaño pequeño:
Recorta o limita títulos:
const title = project.title.slice(0, 80);Mejor aún, modela un campo editorial específico y define wrapping/truncamiento visual.
React escapa texto renderizado, pero valida longitud y caracteres. Evita insertar HTML arbitrario. No uses contenido de borrador o privado en una imagen pública.
Si la fuente de datos falla, un share puede no tener imagen. Opciones:
Para rutas críticas, un fallback de marca puede ser mejor que un error 500.
Abre directamente:
http://localhost:3000/portfolio/domisys/opengraph-imagePrueba:
Los caches son independientes. Una imagen correcta en browser no garantiza que el crawler pueda accederla; revisa robots, auth, status y headers.
Portfolio general usa una imagen estática. Cada proyecto publicado usa imagen dinámica con:
project.title
project.shortDescription
project.status
brand markDrafts quedan noindex y no generan una preview pública accesible.
No aplica como en DOM normal.
La route falla al generar.
Latencia y fallos.
Fuga por URL compartida.
Preview queda obsoleta en tu servidor.
Sus caches permanecen.
Se recorta o sale del canvas.
ImageResponse usa un subconjunto CSS.id en Next.js 16 al usar generateImageMetadata?Optimización de imágenes con next/image controla bytes, dimensiones, LCP y seguridad de assets dentro de las páginas reales.