
satori-constraints
by shepherdjerred
Create match reports for League of Legends
SKILL.md
name: satori-constraints description: | Satori library constraints for JSX-to-SVG rendering and OG image generation. Use when working with report package, image generation, satori, resvg, CSS styling for images, or font loading.
Satori Constraints Reference
Comprehensive reference for Satori's limitations and best practices when generating images from JSX.
Overview
Satori is Vercel's library that converts HTML/CSS (or JSX) to SVG. It's used for generating OG images, social cards, and report images. This project uses Satori in the @scout-for-lol/report package with @resvg/resvg-js for SVG-to-PNG conversion.
Layout Engine
Flexbox-Based (Yoga)
Satori uses Yoga (the same layout engine as React Native), not browser CSS:
- Only flexbox layout is supported - no CSS Grid, no
float, noposition: absoluteoutside flex containers - Every element with children must use
display: flexexplicitly - Default flex direction is
row(like React Native, unlike CSS default ofblock)
Key Differences from Browser CSS
| Feature | Satori | Browser CSS |
|---|---|---|
| Default display | flex | block |
| Default flex-direction | row | row |
position: absolute | Relative to flex parent | Relative to positioned ancestor |
z-index | Not supported | Supported |
| 3D transforms | Not supported | Supported |
Supported CSS Properties
Layout Properties
display(onlyflexandnone)position(relative,absolute)top,right,bottom,leftmargin,padding(all variants)width,height,min-*,max-*flex,flex-grow,flex-shrink,flex-basisflex-direction,flex-wrapalign-items,align-self,align-contentjustify-contentgapoverflow(hidden,visible)
Visual Properties
colorbackground,background-color,background-imageborder,border-radiusbox-shadowopacity
Typography
font-family,font-size,font-weight,font-styleline-heightletter-spacingtext-aligntext-decorationtext-transformwhite-spaceword-breaktext-overflow
NOT Supported
z-index- elements render in DOM ordertransformwith 3D functions (rotateX,translateZ, etc.)animation,transitioncursorfilter(partial support)- Advanced typography (kerning, ligatures, OpenType features)
- RTL text direction
- CSS Grid
float::before,::afterpseudo-elements- Media queries
- CSS variables
Font Requirements
Supported Formats
| Format | Supported | Notes |
|---|---|---|
| TTF | Yes | Recommended for server-side |
| OTF | Yes | Recommended for server-side |
| WOFF | Yes | Good balance of size/speed |
| WOFF2 | No | Not supported (opentype.js limitation) |
Loading Fonts
Fonts must be passed as ArrayBuffer (browser) or Buffer (Node.js):
import satori from 'satori';
const fontData = await Bun.file('./fonts/Inter-Regular.ttf').arrayBuffer();
const svg = await satori(
<div style={{ fontFamily: 'Inter' }}>Hello</div>,
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: fontData,
weight: 400,
style: 'normal',
},
],
}
);
Best Practices
- Define fonts globally - don't create new font objects per render
- Use TTF/OTF on server - faster to parse than WOFF
- Multiple weights - pass each weight as separate font entry
- Fallback fonts - provide fallbacks for missing characters
Image Handling
Image Requirements
// REQUIRED: width and height for external URLs
<img
src="https://example.com/image.png"
width={100}
height={100}
/>
// RECOMMENDED: base64 for best performance
<img
src="data:image/png;base64,iVBORw0KGgoAAAANS..."
width={100}
height={100}
/>
Best Practices
- Use base64 - avoids extra network requests during rendering
- Always set dimensions -
widthandheightare required for external URLs - Fetch and convert - for dynamic images, fetch then convert to base64:
async function imageToBase64(url: string): Promise<string> {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
const base64 = Buffer.from(buffer).toString('base64');
const mimeType = response.headers.get('content-type') || 'image/png';
return `data:${mimeType};base64,${base64}`;
}
Background Images
<div
style={{
backgroundImage: 'url(data:image/png;base64,...)',
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
Emoji Support
Satori supports emoji through dynamic loading:
Using graphemeImages
const svg = await satori(
<div>Hello World! </div>,
{
width: 600,
height: 400,
fonts: [...],
graphemeImages: {
'': 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/1f44b.svg',
},
}
);
Using loadAdditionalAsset
const svg = await satori(element, {
width: 1200,
height: 630,
fonts: [...],
loadAdditionalAsset: async (code, segment) => {
if (code === 'emoji') {
// Fetch Twemoji SVG and return as data URL
const emojiCode = getEmojiCodePoint(segment);
const response = await fetch(
`https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/${emojiCode}.svg`
);
const svg = await response.text();
return `data:image/svg+xml;base64,${btoa(svg)}`;
}
return null;
},
});
Known Issues & Workarounds
Transparent Gradients Render as Black
Bug: linear-gradient(transparent, white) renders as dark gray to white.
Workaround: Use rgba(255, 255, 255, 0) instead of transparent:
backgroundImage: 'linear-gradient(rgba(255, 255, 255, 0), white)'
No z-index Support
Issue: Elements render in DOM order, no z-index control.
Workaround: Order elements correctly in JSX (later elements render on top).
Text Overflow
Issue: Long text may overflow containers unexpectedly.
Workaround: Always set overflow: hidden and use text-overflow: ellipsis:
<div style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: 400,
}}>
Long text that might overflow...
</div>
Inconsistent Font Metrics
Issue: Text positioning may differ slightly from browser rendering.
Workaround: Test with actual fonts, add padding for safety margins.
SVG to PNG Conversion
Using @resvg/resvg-js
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
// Generate SVG with Satori
const svg = await satori(element, options);
// Convert to PNG
const resvg = new Resvg(svg, {
fitTo: {
mode: 'width',
value: 1200,
},
});
const pngData = resvg.render();
const pngBuffer = pngData.asPng();
Performance Tips
- Lazy load - import satori and resvg only when needed
- Cache fonts - load fonts once at startup
- Reuse Resvg instances - when generating many images
- Set appropriate dimensions - larger images = longer render time
Common Patterns
OG Image Template
const OGImage = ({ title, description }: Props) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
padding: 60,
backgroundColor: '#1a1a2e',
color: 'white',
fontFamily: 'Inter',
}}
>
<div style={{
display: 'flex',
fontSize: 64,
fontWeight: 700,
marginBottom: 20,
}}>
{title}
</div>
<div style={{
display: 'flex',
fontSize: 32,
color: '#a0a0a0',
}}>
{description}
</div>
</div>
);
const svg = await satori(<OGImage title="Hello" description="World" />, {
width: 1200,
height: 630,
fonts: [{ name: 'Inter', data: fontData, weight: 400, style: 'normal' }],
});
Debugging Layout
When layout isn't working as expected:
- Add
border: '1px solid red'to containers - Check that all containers have
display: 'flex' - Verify flex direction (default is
row) - Check for missing dimensions on images
Sources
- Satori GitHub Repository
- Satori README
- Vercel OG Image Generation Docs
- Vercel Blog - Introducing OG Image Generation
- Satori NPM Package
- Satori DeepWiki
- Next.js ImageResponse
- WOFF2 Support Discussion
- Image Handling Issue
- Emoji Support Issue
- Gradient Bug
- Resvg-js GitHub
- Generate Images with Satori and Resvg
- Implement Emojis in Satori
- Satori Fit Text
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です
