Conception SVG : Satori + SVGO
Vérifié 2026-10 : satori 0.63, @resvg/resvg-js 2.6, svgo 4.1.
Satori — balisage → SVG
npm i satori @resvg/resvg-js
import satori from 'satori'
import { Resvg } from '@resvg/resvg-js'
import { readFile, writeFile } from 'node:fs/promises'
const font = await readFile('./Inter-Bold.ttf') // TTF, OTF, WOFF ou WOFF2
const svg = await satori(
<div style={{ display: 'flex', width: '100%', height: '100%', alignItems: 'center',
justifyContent: 'center', background: '#0b1020', color: 'white', fontSize: 96 }}>
Đủ 20/20
</div>,
{ width: 1920, height: 1080, fonts: [{ name: 'Inter', data: font, weight: 700, style: 'normal' }] },
)
const png = new Resvg(svg, { fitTo: { mode: 'width', value: 1920 } }).render().asPng()
await writeFile('title.png', png)
- Sans JSX/React, passez des objets simples :
{ type: 'div', props: { style: {...}, children: '...' } }. - Les polices sont obligatoires dès qu'il y a du texte ; le vietnamien nécessite une police possédant les glyphes (Inter, Be Vietnam Pro, Noto Sans).
- Disposition : Flexbox, Grid (pas de subgrid/masonry) et block. C'est un sous-ensemble CSS — pas de JS, pas d'animations, pas de feuilles de style externes, tout en ligne.
- Images :
<img src>avec une URL absolue ou une data URI, pluswidth/heightexplicites. - Le texte devient
<path>par défaut (portable, non sélectionnable) ;embedFont: falseconserve<text>. - Emoji :
graphemeImagesouloadAdditionalAssetpour fournir des images/polices pour les segments manquants. - Alternative PNG :
sharp(Buffer.from(svg)).png().
SVGO v4 — optimiser
npx svgo in.svg -o out.svg
npx svgo -f ./icons -o ./icons-min
// svgo.config.mjs
export default {
multipass: true,
plugins: [
{ name: 'preset-default', params: { overrides: {
// conserver ces éléments lorsque le SVG sera animé ou stylé par id/class :
cleanupIds: false, // renommerait/supprimerait les ids ciblés par votre code
collapseGroups: false, // aplatirait le <g> que vous animez comme unité
mergePaths: false, // fusionnerait les chemins que vous animez séparément
convertShapeToPath: false, // transformerait <circle>/<rect> en <path> (plus de r, cx, width)
} } },
'prefixIds', // ids uniques lorsque plusieurs SVG sont intégrés dans une même page
],
}
- Changements v4 :
removeViewBoxetremoveTitlene sont plus dans preset-default (ajoutez-les par leur nom si souhaité ; conservez le viewBox pour le redimensionnement).removeScriptElement→removeScripts. Exports nommés uniquement :import { optimize } from 'svgo'; la version navigateur estsvgo/browser. - API Node :
const { data } = optimize(svgString, { multipass: true, plugins: [...] }). - Pour des icônes statiques, le simple
preset-defaultsuffit ; les surcharges ci-dessus ne sont utiles que pour les SVG animés ou scripés. - Après avoir optimisé un SVG pour l'animation, vérifiez que chaque sélecteur utilisé par le code d'animation existe toujours dans la sortie.