Saltar al contenido principal
Volver al blog

2 min de lectura

Por qué tu OG image crashea con fuentes variables

Satori no carga WOFF2 ni fuentes variables con tabla fvar multi-eje. La salida no fue pelear con la configuración, sino entender qué formato de fuente espera realmente el renderizador.

  • astro
  • og-images
  • satori
  • fuentes

Quería imágenes Open Graph únicas por proyecto e idioma, generadas en el servidor en vez de diseñadas a mano. La receta habitual: Satori convierte un árbol tipo HTML/CSS en SVG, y resvg rasteriza ese SVG a PNG. Sobre el papel, una tarde de trabajo. En la práctica, dos crashes seguidos antes de ver el primer pixel.

Crash uno: el binding nativo que rompe el dev server

@resvg/resvg-js no es JavaScript puro: trae un binario .node compilado. El optimizador de dependencias de Vite (esbuild) intenta meterlo en su pre-bundle y se cae, porque esbuild no sabe empaquetar un addon nativo. El build de producción pasaba, pero pnpm dev ni siquiera arrancaba el endpoint.

La solución fue decirle a Vite que no lo toque: marcar el paquete en optimizeDeps.exclude y en ssr.external dentro de astro.config.mjs. Una vez fuera del pre-bundle, el binding se carga vía require nativo y todo funciona igual en dev que en producción.

Crash dos: la fuente que el navegador adora y Satori rechaza

El sitio ya servía Open Sans como fuente variable en formato WOFF2. Reutilicé ese mismo archivo para Satori y obtuve un error críptico sobre una firma OpenType desconocida. El motivo es simple cuando lo ves: WOFF2 comprime las tablas de la fuente con Brotli, y el parser de OpenType que usa Satori no descomprime Brotli. El navegador sí; Satori no. Son dos pipelines distintos.

Pasé a un TTF. Otro crash, esta vez por la tabla fvar: una fuente variable guarda todos los pesos en un solo archivo con ejes interpolables, y Satori no resuelve las instancias nombradas. No sabe qué hacer con “un peso entre 300 y 800”; quiere un número y una tabla de glifos concreta.

La salida: instancias estáticas por peso

La solución no fue una bandera mágica, sino entregar exactamente lo que el renderizador espera. Exporté dos instancias estáticas desde la fuente variable —OpenSans-Regular (400) y OpenSans-Bold (700)— como TTF planos, y registré cada una con su peso explícito al instanciar Satori. Sin fvar, sin Brotli, sin ambigüedad.

Lo que me llevo

El pipeline de fuentes del runtime no es el del navegador. El navegador es generoso: WOFF2, fuentes variables, todo le viene bien. Un renderizador como Satori es literal y quiere TTF estático, un archivo por peso. Cuando una librería de bajo nivel “no soporta” un formato, casi nunca es un bug suyo: es una pista de qué nivel de abstracción esperaba recibir.