Satori
repository·main·Indexed 12 days ago
https://github.com/vercel/satoriA library to convert HTML and CSS into SVG using JSX syntax, commonly used for generating dynamic social cards and Open Graph images. It utilizes the Yoga layout engine (Flexbox) and supports a subset of HTML elements and CSS properties, including CSS variables and transforms. Satori handles layout calculation, font management, and typography automatically, supporting TTF, OTF, and WOFF formats.
What's inside Satori
- Satori is a library designed to convert HTML and CSS into SVG. It uses JSX syntax to define the layout and styles, making it straightforward to generate SVGs that match browser-based HTML/CSS rendering. It handles complex tasks like layout calculation, font management, and typography automatically.
Supported HTML Elements and Images
mainSatori supports a limited subset of HTML elements. It does not support
<input>,<style>tags, or external resources via<link>or<script>.Images
You can use
<img>to embed images. It is highly recommended to providewidthandheightattributes.For better performance when rendering to other image formats (like PNG), use base64 encoded data, an
ArrayBuffer, or aBufferfor thesrcprop to avoid extra I/O during Satori's execution.await satori( <img src="https://picsum.photos/200/300" width={200} height={300} />, options ) // Using base64 for better performance: await satori( <img src="data:image/png;base64,..." width={200} height={300} />, options )Setup Satori Standalone Build
mainIn environments with limited WASM loading (like Cloudflare Workers), use the
satori/standalonebuild. You must manually load theyoga.wasmbinary and initialize Satori usinginit()before callingsatori().import satori, { init } from 'satori/standalone' const res = await fetch('https://unpkg.com/satori/yoga.wasm') const yogaWasm = await res.arrayBuffer() await init(yogaWasm) // Now you can use satori as usual const svg = await satori(...)Convert JSX to SVG with satori()
mainTo generate an SVG, import
satoriand call it with a JSX element and an options object. The options object must includewidth,height, and afontsarray.Each font object in the
fontsarray requires:name: The font name.data: The font data as aBufferorArrayBuffer(retrieved viafsin Node.js orfetchin other environments).weight: The font weight (e.g.,400).style: The font style (e.g.,'normal').
The function returns an SVG string.
import satori from 'satori' const svg = await satori( <div style={{ color: 'black' }}>hello, world</div , { width: 600, height: 400, fonts: [ { name: 'Roboto', // Use `fs` (Node.js only) or `fetch` to read the font as Buffer/ArrayBuffer and provide `data` here. data: robotoArrayBuffer, weight: 400, style: 'normal', }, ], }, )Use JSX with Satori
mainSatori accepts JSX elements that are pure and stateless. Note that React APIs like
useState,useEffect, anddangerouslySetInnerHTMLare not supported.Experimental: Built-in JSX support
You can use Satori's experimental JSX runtime without installing React by using
@jsxImportSourcepragmas. This allows you to use Satori's own JSX implementation.Use without JSX
If you do not have a JSX transpiler, you can pass React-element-like objects directly. These objects must include
type,props.children, andprops.style./** @jsxRuntime automatic */ /** @jsxImportSource satori/jsx */ import satori from 'satori'; import { FC, JSXNode } from 'satori/jsx'; const MyComponent: FC<{ children: JSXNode }> = ({ children }) => ( <div style={{ color: 'black' }}>{children}</div> ) const svg = await satori( <MyComponent>hello, world</MyComponent>, options, ) // OR without JSX: await satori( { type: 'div', props: { children: 'hello, world', style: { color: 'black' }, }, }, options )Configure JSX for Satori using @jsxImportSource
mainTo use JSX with Satori, you must tell your compiler (like TypeScript or Babel) to use Satori's JSX runtime. You can do this by adding the
@jsxImportSourcepragma directive at the top of your files containing JSX.Note: Satori does not support class components; only functional components are supported.
/** @jsxImportSource satori/jsx */ function MyComponent() { return <div style={{ color: 'red' }}>Hello Satori</div>; }Dynamically Load Emojis and Fonts
mainUse the
loadAdditionalAssetcallback to fetch missing assets (fonts or emojis) on the fly. The callback receives acode(e.g.,'emoji', a language code, or'unknown') and thesegmentof text being rendered.await satori( <div>👋 你好</div>, { loadAdditionalAsset: async (code: string, segment: string) => { if (code === 'emoji') { return `data:image/svg+xml;base64,...` } return loadFontFromSystem(code) } } )Configure Fonts and Typography
mainSatori supports TTF, OTF, and WOFF formats (WOFF2 is not currently supported). You must provide font data as an
ArrayBuffer(browser) orBuffer(Node.js).Multiple fonts can be passed in the
fontsarray and referenced viafontFamilyin your styles.await satori( <div style={{ fontFamily: 'Inter' }}>Hello</div>, { width: 600, height: 400, fonts: [ { name: 'Inter', data: inter, weight: 400, style: 'normal', }, { name: 'Inter', data: interBold, weight: 700, style: 'normal', }, ], } )Render Emojis and Locales
mainEmojis
Use the
graphemeImagesoption to map specific emojis (graphemes) to image sources. The image will be resized to match the current font size as a square.Locales
Specify the language via the
langattribute on elements to ensure correct rendering for specific locales.await satori( <div style={{ fontFamily: 'Inter' }}>Next.js is 🤯!</div>, { graphemeImages: { '🤯': 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/1f92f.svg', }, } ) // Using lang attribute for locales: await satori( <div lang="ja-JP">骨</div> )Configure Font Embedding and Precision
mainFont Embedding
By default, Satori renders text as
<path>elements in the SVG to embed font data directly. To use standard<text>elements instead, setembedFont: false.Pixel Grid Rounding
Use
pointScaleFactorto control how layout values are rounded to the pixel grid. This improves rendering precision on high-DPI displays.Debugging
Pass
debug: trueto draw bounding boxes around elements for layout debugging.const svg = await satori( <div style={{ color: 'black' }}>hello, world</div>, { embedFont: false, pointScaleFactor: 2, debug: true, }, )Configure fonts with FontOptions
mainWhen loading fonts into Satori via the
FontLoader, you must provide an array ofFontOptionsobjects. Each object specifies the raw font data and its metadata to ensure correct rendering and fallback behavior.Key properties:
data: The raw font file as aBufferorArrayBuffer.name: The font family name (e.g.,'sans-serif').weight: The numeric weight (e.g.,400,700) or aWeightName('normal','bold').style: The font style ('normal'or'italic').lang: An optional locale string (e.g.,'ja') to associate the font with a specific language. If not set, it defaults to an internalunknownsuffix.
const fontOptions: FontOptions[] = [ { name: 'Inter', data: fontBuffer, weight: 400, style: 'normal' }, { name: 'Inter', data: boldFontBuffer, weight: 700, style: 'normal' } ];CSS Support and Layout Engine
mainSatori uses the Yoga layout engine (Flexbox) and is not a complete CSS implementation.
Key Limitations:
- No
z-indexsupport (elements later in the document are painted on top). - No
calc()support. currentColoris only supported for thecolorproperty.- Three-dimensional transforms are not supported.
- No support for advanced typography (kerning, ligatures) or RTL languages.
Supported Features:
- CSS Variables: Supported via
--var-nameandvar(--var-name)with fallbacks. - Layout:
display(flex,contents,none),position(relative,static,absolute),flexDirection,flexWrap,alignItems,justifyContent,gap, etc. - Box Model:
margin,padding,width,height,min/maxsizes (exceptmin-content,max-content,fit-content),border,borderRadius,boxSizing,boxShadow. - Text:
fontFamily,fontSize,fontWeight,textAlign,textDecoration,lineHeight,letterSpacing,whiteSpace,wordBreak,textWrap(wrap,balance). - Background:
backgroundColor,backgroundImage(gradients andurl),backgroundSize,backgroundRepeat. - Transforms:
translate,rotate,scale,skew. - Other:
opacity,filter,clipPath,objectFit,objectPosition,overflow(visible,hidden).
- No