Cómo generar OG Images rápidas con Satori + Resvg en Astro
Generar imágenes de Open Graph (OG) dinámicas con código ya es una práctica estándar, pero hacerlo mal puede afectar el rendimiento de tu CI/CD o romper tus Edge Functions en producción.
Satori simplifica el diseño porque permite usar JSX y Tailwind como si estuvieras creando un componente normal de React. El problema: Satori no genera una imagen final, produce un SVG. Para que Twitter, LinkedIn o Facebook rendericen correctamente la tarjeta, necesitás convertir ese SVG a PNG con Resvg.
Satori (JSX → SVG)
↓
Resvg WASM (SVG → PNG)
↓
Edge Function / CDNSatori dibuja. Resvg renderiza.
Satori + Resvg vs. Puppeteer#
| RecomendadoSatori + Resvg | Puppeteer | |
|---|---|---|
| Runtime | Ligero, sin dependencias grandes | Requiere levantar Chromium |
| Renderizado | SVG → PNG vía WASM | Browser completo |
| Compatibilidad Edge | ✓✓ | ✗ |
| Performance | Milisegundos | Cold starts más altos |
| CSS | Soporte parcial | Soporte completo |
| Complejidad de setup | Baja | Alta |
Puppeteer sigue siendo la mejor opción si necesitás soporte CSS completo o layouts extremadamente complejos. Para la mayoría de las OG Images modernas, Satori + Resvg resuelve con un overhead considerablemente menor.
Implementación en Astro#
import satori from "satori";
import { Resvg } from "@resvg/resvg-wasm";
import type { APIRoute } from "astro";
export const GET: APIRoute = async ({ params }) => {
const { slug } = params;
const title = slug
? slug.replace(/-/g, " ")
: "Mi Post";
// 1. Generamos el SVG con Satori
const svg = await satori(
<div tw="flex w-full h-full bg-slate-900 items-center justify-center">
<h1 tw="text-white text-6xl font-bold">
{title}
</h1>
</div>,
{
width: 1200,
height: 630,
fonts: [
// Cargar fuente manualmente
],
}
);
// 2. Convertimos SVG → PNG
const resvg = new Resvg(svg, {
fitTo: {
mode: "width",
value: 1200,
},
});
const pngData = resvg.render().asPng();
// 3. Devolvemos imagen cacheable
return new Response(pngData, {
headers: {
"Content-Type": "image/png",
"Cache-Control":
"public, max-age=31536000, immutable",
},
});
};El verdadero cuello de botella#
El problema no suele ser Satori.
El problema es el runtime.
Las Edge Functions tienen límites de CPU y memoria agresivos, especialmente en planes gratuitos. Si el layout tiene demasiadas capas, imágenes pesadas o varias fuentes custom, el render puede exceder el tiempo permitido y devolver errores intermitentes.
Cuando el diseño empieza a parecer una landing page comprimida en 1200×630, mover el endpoint a una Serverless Function tradicional en Node.js es la salida más sensata. Agregás algo de cold start; a cambio, tenés CPU sin el límite agresivo del Edge.
La parte interesante para GEO#
Las OG Images controlan la capa visual de distribución en redes sociales. Los LLMs no procesan esa capa: procesan estructura semántica. Por eso cumplen funciones distintas y no son intercambiables.
| Capa | Objetivo |
|---|---|
| OG Images | Distribución visual y CTR |
| JSON-LD | Comprensión semántica e indexación AI |
El TIL de citabilidad: datos para el index de IA cubre la capa semántica. Y para entender por qué el indexado de Google cambió, el TIL de Google ya no indexa como antes da el contexto estratégico completo.