Cuando compartes un enlace en WhatsApp, LinkedIn o X, la red muestra una imagen. Si no la defines, muestra algo al azar o nada. Hacerlas a mano para cada artículo es un trabajo que nadie mantiene. Nosotros las generamos con código: una por artículo y por idioma, con el titular dentro y la marca.
Esa imagen es la og:image de la página. Ya no se muestra dentro del artículo, donde repetía el título que está justo encima, pero sigue siendo lo que ven quienes comparten el enlace.
Las herramientas
next/og: la claseImageResponse, que convierte un árbol de elementos de React en una imagen. Por debajo usa Satori.sharp: para convertir esa imagen a WebP y comprimirla.- Un script que recorre los artículos y escribe los archivos.
Se podría usar el archivo especial opengraph-image de Next.js, que genera la imagen al pedirla. Elegimos un script por una razón práctica: así la imagen es un archivo, se sube a la biblioteca de medios del CMS y se puede reutilizar como meta.image, sin cálculo en cada petición.
Paso 1: el diseño es un componente
La portada es un componente de React con estilos en línea. Una versión mínima:
function Cover({ title }: { title: string }) {
return (
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
padding: '80px 96px',
background: '#0B0F1A',
color: '#EEF1F7',
}}
>
<span style={{ fontFamily: 'Mono', fontSize: 28 }}>codegea</span>
<div style={{ fontFamily: 'Sans', fontWeight: 500, fontSize: 112 }}>{title}</div>
</div>
);
}Satori entiende un subconjunto de CSS: maquetación con flexbox, sin cuadrícula ni muchas propiedades modernas. Si algo no se ve, casi siempre es porque falta un display: 'flex'.
Paso 2: las fuentes
Satori no usa las fuentes del sistema: hay que pasarle los archivos. Nosotros descargamos las tres de marca una vez y las guardamos en una carpeta de caché:
const fonts = await loadFonts(CACHE);
// [{ name: 'Sans', data, weight: 500 }, { name: 'Serif', … }, { name: 'Mono', … }]Luego se las das a ImageResponse en la opción fonts y las usas por su nombre en fontFamily.
Paso 3: de componente a archivo
const res = new ImageResponse(<Cover title={title} />, {
width: 1600,
height: 900,
fonts,
});
const png = Buffer.from(await res.arrayBuffer());
await sharp(png).webp({ quality: 84 }).toFile(`covers/${slug}.${locale}.webp`);Un detalle: Satori genera un PNG, que pesa bastante. Pasarlo por sharp a WebP lo deja en una fracción del tamaño. Nosotros usamos 1600×900; para las redes se recomienda un formato apaisado de proporción cercana a 1,91:1 (por ejemplo, 1200×630). Si prefieres ese formato, cambia las dimensiones.
Paso 4: el titular con una palabra destacada
Los titulares del sitio marcan una palabra con asteriscos para mostrarla en cursiva serif: Cómo generar *imágenes para compartir* con código. Una función separa el texto en palabras normales y destacadas, y el componente aplica otro estilo a las segundas:
{words(title).map((w, i) => (
<span key={i} style={w.em ? { fontFamily: 'Serif', fontStyle: 'italic', color: '#5C7CF5' } : {}}>
{w.text}
</span>
))}Paso 5: una por artículo y por idioma
El script lee el frontmatter de cada artículo, toma coverTitle (o title si no hay), calcula los minutos de lectura del cuerpo y genera la imagen de cada idioma. Después escribe la ruta del archivo en el propio artículo:
pnpm covers # solo las que no tienen portada
pnpm covers mi-articulo # regenera una, en es y en
pnpm covers --force # todasComo cada imagen lleva el titular dentro, hay una por idioma; no sirve la misma para el artículo en español y en inglés.
Lo que aprendimos
- El texto largo se corta. Ajustamos el tamaño de letra según cuántos caracteres tiene el titular. Sin eso, un título largo se sale del cuadro.
- Prueba la imagen en las redes. Cada red guarda en caché la imagen anterior; para ver el cambio tienes que pedirles que vuelvan a leer el enlace con sus herramientas de depuración.
- No metas fotos de terceros ni logos ajenos. Una imagen de marca con tipografía es suficiente y evita problemas de derechos.
Si quieres ver cómo se conectan con el resto del sitio, mira así está construido codegea.com, y para revisar el resto del SEO de tus páginas, títulos y meta descriptions que suben el CTR.
Escrito por
Derian Pinto
Founder & Full Stack Developer
Fundador de Codegea. Desarrolla sitios, tiendas y plataformas a medida, e integra y da soporte a Arc XP para medios.