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.