Sugerencias
← TIL
~3 min de lectura
#astro#seo#og-images#satori

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.

Pipeline OG
TEXT
Satori (JSX → SVG)

Resvg WASM (SVG → PNG)

Edge Function / CDN

Satori dibuja. Resvg renderiza.

Satori + Resvg vs. Puppeteer#

Satori + Resvg vs. Puppeteer
RecomendadoSatori + ResvgPuppeteer
RuntimeLigero, sin dependencias grandesRequiere levantar Chromium
RenderizadoSVG → PNG vía WASMBrowser completo
Compatibilidad Edge✓✓
PerformanceMilisegundosCold starts más altos
CSSSoporte parcialSoporte completo
Complejidad de setupBajaAlta

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#

src/pages/og/[slug].png.ts
TS
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.

OG Images vs JSON-LD
CapaObjetivo
OG ImagesDistribución visual y CTR
JSON-LDComprensió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.

Enlace copiado al portapapeles