Top Related Projects
JavaScript API for Chrome and Firefox
Automated auditing, performance metrics, and best practices for the web.
Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
Fast, easy and reliable testing for anything that runs in a browser.
A JavaScript implementation of various web standards, for use with Node.js
Quick Overview
Satori is an open-source library developed by Vercel that converts HTML and CSS to SVG. It enables developers to generate images from HTML and CSS, making it useful for creating social media cards, open graph images, and other dynamic image content programmatically.
Pros
- Renders HTML and CSS to SVG, allowing for complex layouts and designs
- Supports custom fonts and emoji rendering
- Can be used in both server-side and client-side environments
- Highly customizable with various configuration options
Cons
- Limited browser compatibility as it relies on modern web technologies
- May have performance issues with very complex layouts or large amounts of content
- Requires additional setup for certain features like custom fonts
- Learning curve for users unfamiliar with HTML and CSS for image generation
Code Examples
- Basic usage:
import satori from 'satori'
const svg = await satori(
<div style={{ color: 'black' }}>Hello, World!</div>,
{ width: 600, height: 400 }
)
- Using custom fonts:
import satori from 'satori'
import fs from 'fs'
const fontData = fs.readFileSync('./my-font.ttf')
const svg = await satori(
<div style={{ fontFamily: 'My Font' }}>Custom font text</div>,
{
width: 600,
height: 400,
fonts: [
{
name: 'My Font',
data: fontData,
weight: 400,
style: 'normal',
},
],
}
)
- Creating a social media card:
import satori from 'satori'
const svg = await satori(
<div style={{ display: 'flex', flexDirection: 'column', padding: '20px', backgroundColor: '#f0f0f0' }}>
<h1 style={{ fontSize: '24px', color: '#333' }}>My Awesome Blog Post</h1>
<p style={{ fontSize: '16px', color: '#666' }}>Check out this amazing content!</p>
</div>,
{
width: 1200,
height: 630,
}
)
Getting Started
To use Satori in your project, follow these steps:
- Install Satori:
npm install satori
- Import and use Satori in your code:
import satori from 'satori'
const svg = await satori(
<div style={{ color: 'black' }}>Hello, Satori!</div>,
{ width: 600, height: 400 }
)
// Use the generated SVG as needed (e.g., save to file, convert to PNG, etc.)
Note: Satori returns an SVG string. You may need additional tools to convert it to other formats or display it in your application.
Competitor Comparisons
JavaScript API for Chrome and Firefox
Pros of Puppeteer
- More comprehensive browser automation capabilities, allowing for complex interactions and testing
- Supports a wider range of use cases beyond image generation, including web scraping and PDF generation
- Mature project with extensive documentation and community support
Cons of Puppeteer
- Heavier resource usage due to launching a full browser instance
- Slower execution time for simple tasks like image generation
- Requires more setup and configuration for basic operations
Code Comparison
Puppeteer (generating an image):
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent('<h1>Hello World</h1>');
await page.screenshot({ path: 'example.png' });
await browser.close();
Satori (generating an image):
const svg = await satori(
<div style={{ color: 'black' }}>
<h1>Hello World</h1>
</div>,
{ width: 600, height: 400 }
);
Summary
Puppeteer offers more extensive browser automation features but comes with higher resource usage and complexity. Satori, on the other hand, is specifically designed for generating images from HTML and CSS, providing a simpler and more lightweight solution for this particular use case. The choice between the two depends on the specific requirements of your project and the range of functionalities needed.
Automated auditing, performance metrics, and best practices for the web.
Pros of Lighthouse
- Comprehensive web performance auditing tool with a focus on Core Web Vitals
- Integrates well with Chrome DevTools and offers CLI functionality
- Provides detailed reports and suggestions for improving website performance
Cons of Lighthouse
- More complex setup and usage compared to Satori's focused approach
- Primarily designed for web performance auditing, not image generation
- May require more resources and time to run full audits
Code Comparison
Lighthouse (running an audit):
const chromeLauncher = require('chrome-launcher');
const lighthouse = require('lighthouse');
(async () => {
const chrome = await chromeLauncher.launch({chromeFlags: ['--headless']});
const options = {logLevel: 'info', output: 'json', onlyCategories: ['performance'], port: chrome.port};
const runnerResult = await lighthouse('https://example.com', options);
console.log('Report is done for', runnerResult.lhr.finalUrl);
await chrome.kill();
})();
Satori (generating an image):
import satori from 'satori';
const svg = await satori(
<div style={{ color: 'black' }}>Hello, World!</div>,
{ width: 600, height: 400, fonts: [{ name: 'Roboto', data: fontData, weight: 400, style: 'normal' }] }
);
Note: The code examples demonstrate the primary use cases for each tool, highlighting their different focuses and implementations.
Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
Pros of Playwright
- Comprehensive browser automation tool supporting multiple browsers (Chromium, Firefox, WebKit)
- Powerful API for end-to-end testing, scraping, and automating web applications
- Strong community support and regular updates from Microsoft
Cons of Playwright
- Steeper learning curve due to its extensive feature set
- Larger package size and potentially higher resource usage
- Not specifically designed for generating images or SVGs
Code Comparison
Playwright (browser automation):
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
Satori (SVG generation):
import satori from 'satori';
const svg = await satori(
<div style={{ color: 'black' }}>Hello, World!</div>,
{ width: 600, height: 400 }
);
Summary
While Playwright excels in browser automation and testing, Satori focuses on generating SVGs from HTML and CSS. Playwright offers a more comprehensive solution for web interactions, but Satori provides a simpler API for creating images programmatically. The choice between them depends on the specific use case: Playwright for testing and automation, Satori for image generation.
Fast, easy and reliable testing for anything that runs in a browser.
Pros of Cypress
- Comprehensive end-to-end testing framework for web applications
- Extensive documentation and active community support
- Real-time browser testing with automatic waiting and retries
Cons of Cypress
- Limited to testing web applications, not suitable for image generation
- Steeper learning curve for beginners due to its extensive feature set
- Requires more setup and configuration compared to simpler tools
Code Comparison
Cypress (JavaScript):
describe('Login Test', () => {
it('should log in successfully', () => {
cy.visit('/login')
cy.get('#username').type('testuser')
cy.get('#password').type('password123')
cy.get('#submit').click()
cy.url().should('include', '/dashboard')
})
})
Satori (JavaScript):
import satori from 'satori'
const svg = await satori(
<div style={{ color: 'black' }}>Hello, World!</div>,
{ width: 600, height: 400 }
)
While Cypress focuses on web application testing, Satori is designed for generating images from HTML and CSS. The code examples highlight their different purposes, with Cypress demonstrating a login test and Satori showing image generation from JSX. Cypress offers a more comprehensive testing solution, while Satori provides a simpler API for creating images programmatically.
A JavaScript implementation of various web standards, for use with Node.js
Pros of jsdom
- More comprehensive DOM simulation, including full browser-like environment
- Supports a wider range of web technologies and APIs
- Extensively tested and widely used in the community
Cons of jsdom
- Heavier and slower compared to Satori's lightweight approach
- May include unnecessary features for simple HTML parsing tasks
- Requires more setup and configuration for basic use cases
Code Comparison
jsdom:
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`<p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent);
Satori:
import satori from 'satori';
const svg = await satori(
<div>Hello world</div>,
{ width: 600, height: 400 }
);
Key Differences
- jsdom provides a full DOM environment, while Satori focuses on generating SVGs from HTML and CSS
- jsdom is better suited for complex web application testing, while Satori excels at creating static images from markup
- Satori offers a more streamlined API for specific use cases, whereas jsdom provides a broader range of functionality
Use Cases
- Choose jsdom for comprehensive web application testing and scenarios requiring a full browser-like environment
- Opt for Satori when generating static images or SVGs from HTML and CSS, especially for social media cards or thumbnails
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Satori: Enlightened library to convert HTML and CSS to SVG.
Note
To use Satori in your project to generate PNG images like Open Graph images and social cards, check out our announcement and Vercelâs Open Graph Image Generation â
To use it in Next.js, take a look at the Next.js Open Graph Image Generation template â
Overview
Satori supports the JSX syntax, which makes it very straightforward to use. Hereâs an overview of the basic usage:
// api.jsx
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',
},
],
},
)
Satori will render the element into a 600Ã400 SVG, and return the SVG string:
'<svg ...><path d="..." fill="black"></path></svg>'
Under the hood, it handles layout calculation, font, typography and more, to generate a SVG that matches the exact same HTML and CSS in a browser.
Documentation
JSX
Satori only accepts JSX elements that are pure and stateless. You can use a subset of HTML
elements (see section below), or custom React components, but React APIs such as useState
, useEffect
, dangerouslySetInnerHTML
are not supported.
Use without JSX
If you don't have JSX transpiler enabled, you can simply pass React-elements-like objects that have type
, props.children
and props.style
(and other properties too) directly:
await satori(
{
type: 'div',
props: {
children: 'hello, world',
style: { color: 'black' },
},
},
options
)
HTML Elements
Satori supports a limited subset of HTML and CSS features, due to its special use cases. In general, only these static and visible elements and properties that are implemented.
For example, the <input>
HTML element, the cursor
CSS property are not in consideration. And you can't use <style>
tags or external resources via <link>
or <script>
.
Also, Satori does not guarantee that the SVG will 100% match the browser-rendered HTML output since Satori implements its own layout engine based on the SVG 1.1 spec.
You can find the list of supported HTML elements and their preset styles here.
Images
You can use <img>
to embed images. However, width
, and height
attributes are recommended to set:
await satori(
<img src="https://picsum.photos/200/300" width={200} height={300} />,
options
)
When using background-image
, the image will be stretched to fit the element by default if you don't specify the size.
If you want to render the generated SVG to another image format such as PNG, it would be better to use base64 encoded image data (or buffer) directly as props.src
so no extra I/O is needed in Satori:
await satori(
<img src="data:image/png;base64,..." width={200} height={300} />,
// Or src={arrayBuffer}, src={buffer}
options
)
CSS
Satori uses the same Flexbox layout engine as React Native, and itâs not a complete CSS implementation. However, it supports a subset of the spec that covers most common CSS features:
Property | Property Expanded | Supported Values | Example |
---|---|---|---|
display |
none and flex , default to flex |
||
position |
relative and absolute , default to relative |
||
color |
Supported | ||
margin | |||
marginTop | Supported | ||
marginRight | Supported | ||
marginBottom | Supported | ||
marginLeft | Supported | ||
Position | |||
top | Supported | ||
right | Supported | ||
bottom | Supported | ||
left | Supported | ||
Size | |||
width | Supported | ||
height | Supported | ||
Min & max size | |||
minWidth | Supported except for min-content , max-content and fit-content | ||
minHeight | Supported except for min-content , max-content and fit-content | ||
maxWidth | Supported except for min-content , max-content and fit-content | ||
maxHeight | Supported except for min-content , max-content and fit-content | ||
border | |||
Width (borderWidth , borderTopWidth , ...) | Supported | ||
Style (borderStyle , borderTopStyle , ...) | solid and dashed , default to solid | ||
Color (borderColor , borderTopColor , ...) | Supported | ||
Shorthand (border , borderTop , ...) | Supported, i.e. 1px solid gray | ||
borderRadius | |||
borderTopLeftRadius | Supported | ||
borderTopRightRadius | Supported | ||
borderBottomLeftRadius | Supported | ||
borderBottomRightRadius | Supported | ||
Shorthand | Supported, i.e. 5px , 50% / 5px | ||
Flex | |||
flexDirection | column , row , row-reverse , column-reverse , default to row | ||
flexWrap | wrap , nowrap , wrap-reverse , default to wrap | ||
flexGrow | Supported | ||
flexShrink | Supported | ||
flexBasis | Supported except for auto | ||
alignItems | stretch , center , flex-start , flex-end , baseline , normal , default to stretch | ||
alignContent | Supported | ||
alignSelf | Supported | ||
justifyContent | Supported | ||
gap | Supported | ||
Font | |||
fontFamily | Supported | ||
fontSize | Supported | ||
fontWeight | Supported | ||
fontStyle | Supported | ||
Text | |||
tabSize | Supported | ||
textAlign | start , end , left , right , center , justify , default to start | ||
textTransform | none , lowercase , uppercase , capitalize , defaults to none | ||
textOverflow | clip , ellipsis , defaults to clip | ||
textDecoration | Support line types underline and line-through , and styles dotted , dashed , solid | Example | |
textShadow | Supported | ||
lineHeight | Supported | ||
letterSpacing | Supported | ||
whiteSpace | normal , pre , pre-wrap , pre-line , nowrap , defaults to normal | ||
wordBreak | normal , break-all , break-word , keep-all , defaults to normal | ||
textWrap | wrap , balance , defaults to wrap | ||
Background | |||
backgroundColor | Supported, single value | ||
backgroundImage | linear-gradient , radial-gradient , url , single value | ||
backgroundPosition | Support single value | ||
backgroundSize | Support two-value size i.e. `10px 20%` | ||
backgroundClip | border-box , text | ||
backgroundRepeat | repeat , repeat-x , repeat-y , no-repeat , defaults to repeat | ||
transform | |||
Translate (translate , translateX , translateY ) | Supported | ||
Rotate | Supported | ||
Scale (scale , scaleX , scaleY ) | Supported | ||
Skew (skew , skewX , skewY ) | Supported | ||
transformOrigin |
Support one-value and two-value syntax (both relative and absolute values) | ||
objectFit |
contain , cover , none , default to none |
||
opacity |
Supported | ||
boxShadow |
Supported | ||
overflow |
visible and hidden , default to visible |
||
filter |
Supported | ||
clipPath |
Supported | Example | |
lineClamp |
Supported | Example | |
Mask | |||
maskImage | linear-gradient(...) , radial-gradient(...) , url(...) | Example | |
maskPosition | Supported | Example | |
maskSize | Support two-value size i.e. `10px 20%` | Example | |
maskRepeat | repeat , repeat-x , repeat-y , no-repeat , defaults to repeat | Example |
Note:
- Three-dimensional transforms are not supported.
- There is no
z-index
support in SVG. Elements that come later in the document will be painted on top. box-sizing
is set toborder-box
for all elements.calc
isn't supported.currentcolor
support is only available for thecolor
property.
Language and Typography
Advanced typography features such as kerning, ligatures and other OpenType features are not currently supported.
RTL languages are not supported either.
Fonts
Satori currently supports three font formats: TTF, OTF and WOFF. Note that WOFF2 is not supported at the moment. You must specify the font if any text is rendered with Satori, and pass the font data as ArrayBuffer (web) or Buffer (Node.js):
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',
},
],
}
)
Multiple fonts can be passed to Satori and used in fontFamily
.
[!TIP] We recommend you define global fonts instead of creating a new object and pass it to satori for better performace, if your fonts do not change. Read it for more detail
Emojis
To render custom images for specific graphemes, you can use graphemeImages
option to map the grapheme to an image source:
await satori(
<div>Next.js is ð¤¯!</div>,
{
...,
graphemeImages: {
'ð¤¯': 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/1f92f.svg',
},
}
)
The image will be resized to the current font-size (both width and height) as a square.
Locales
Satori supports rendering text in different locales. You can specify the supported locales via the lang
attribute:
await satori(
<div lang="ja-JP">骨</div>
)
Same characters can be rendered differently in different locales, you can specify the locale when necessary to force it to render with a specific font and locale. Check out this example to learn more.
Supported locales are exported as the Locale
enum type.
Dynamically Load Emojis and Fonts
Satori supports dynamically loading emoji images (grapheme pictures) and fonts. The loadAdditionalAsset
function will be called when a text segment is rendered but missing the image or font:
await satori(
<div>ð ä½ å¥½</div>,
{
// `code` will be the detected language code, `emoji` if it's an Emoji, or `unknown` if not able to tell.
// `segment` will be the content to render.
loadAdditionalAsset: async (code: string, segment: string) => {
if (code === 'emoji') {
// if segment is an emoji
return `data:image/svg+xml;base64,...`
}
// if segment is normal text
return loadFontFromSystem(code)
}
}
)
Runtime and WASM
Satori can be used in browser, Node.js (>= 16), and Web Workers.
By default, Satori depends on asm.js for the browser runtime, and native module in Node.js. However, you can optionally load WASM instead by importing satori/wasm
and provide the initialized WASM module instance of Yoga to Satori:
import satori, { init } from 'satori/wasm'
import initYoga from 'yoga-wasm-web'
const yoga = initYoga(await fetch('/yoga.wasm').then(res => res.arrayBuffer()))
init(yoga)
await satori(...)
When running in the browser or in the Node.js environment, WASM files need to be hosted and fetched before initializing. asm.js can be bundled together with the lib. In this case WASM should be faster.
When running on the Node.js server, native modules should be faster. However there are Node.js environments where native modules are not supported (e.g. StackBlitz's WebContainers), or other JS runtimes that support WASM (e.g. Vercel's Edge Runtime, Cloudflare Workers, or Deno).
Additionally, there are other difference between asm.js, native and WASM, such as security and compatibility.
Overall there are many trade-offs between each choice, and it's better to pick the one that works the best for your use case.
Font Embedding
By default, Satori renders the text as <path>
in SVG, instead of <text>
. That means it embeds the font path data as inlined information, so succeeding processes (e.g. render the SVG on another platform) donât need to deal with font files anymore.
You can turn off this behavior by setting embedFont
to false
, and Satori will use <text>
instead:
const svg = await satori(
<div style={{ color: 'black' }}>hello, world</div>,
{
...,
embedFont: false,
},
)
Debug
To draw the bounding box for debugging, you can pass debug: true
as an option:
const svg = await satori(
<div style={{ color: 'black' }}>hello, world</div>,
{
...,
debug: true,
},
)
Contribute
You can use the Vercel OG Image Playground to test and report bugs of Satori. Please follow our contribution guidelines before opening a Pull Request.
Author
- Shu Ding (@shuding_)
Top Related Projects
JavaScript API for Chrome and Firefox
Automated auditing, performance metrics, and best practices for the web.
Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
Fast, easy and reliable testing for anything that runs in a browser.
A JavaScript implementation of various web standards, for use with Node.js
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot