Satori — HTML/CSS to SVG for OG Images
You are an expert in Satori and @vercel/og for generating dynamic Open Graph images.
Overview
Satori converts JSX-like HTML and CSS into SVG. @vercel/og wraps Satori with an ImageResponse class that renders the SVG to PNG, designed to run in Vercel Edge Functions and other edge runtimes.
Installation
# For Next.js projects (recommended — includes Satori + PNG rendering)
npm install @vercel/og
# Standalone Satori (SVG output only)
npm install satori
Next.js App Router — OG Image Route (Recommended)
Next.js has built-in OG image support via the ImageResponse re-exported from next/og:
// app/og/route.tsx OR app/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const runtime = 'edge'
export async function GET(request: Request) {
return new ImageResponse(
(
<div
style={{
display: 'flex',
fontSize: 60,
color: 'white',
background: 'linear-gradient(to bottom, #1a1a2e, #16213e)',
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
}}
>
Hello, OG Image!
</div>
),
{ width: 1200, height: 630 }
)
}
Convention-Based OG Images (Next.js 13.3+)
Place an opengraph-image.tsx or twitter-image.tsx file in any route segment:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const alt = 'Blog post image'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export const runtime = 'edge'
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return new ImageResponse(
(
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
background: '#000',
color: '#fff',
fontSize: 48,
}}
>
<div>{post.title}</div>
</div>
),
{ ...size }
)
}
Next.js auto-generates the <meta property="og:image"> tag for these files.
Standalone Satori (SVG Only)
import satori from 'satori'
import { readFileSync } from 'fs'
const svg = await satori(
<div style={{ display: 'flex', color: 'black', fontSize: 40 }}>
Hello from Satori
</div>,
{
width: 1200,
height: 630,
fonts: [
{
name: 'Inter',
data: readFileSync('./fonts/Inter-Regular.ttf'),
weight: 400,
style: 'normal',
},
],
}
)
CSS Support and Limitations
Satori uses a subset of CSS with Flexbox layout (Yoga engine):
Supported:
display: flex(default — all elements are flex containers)- Flexbox properties:
flexDirection,alignItems,justifyContent,flexWrap,gap - Box model:
width,height,padding,margin,border,borderRadius - Typography:
fontSize,fontWeight,fontFamily,lineHeight,letterSpacing,textAlign - Colors:
color,background,backgroundColor,opacity - Backgrounds:
backgroundImage(linear/radial gradients),backgroundClip - Shadows:
boxShadow,textShadow - Transforms:
transform(basic transforms) - Overflow:
overflow: hidden - Position:
absolute,relative - White space:
whiteSpace,wordBreak,textOverflow
Not supported:
display: grid— use nested flex containers instead- CSS animations or transitions
position: fixedorsticky- Pseudo-elements (
::before,::after) - Media queries
- CSS variables
Fonts
Fonts must be loaded explicitly — there are no default system fonts:
// Load font in edge runtime
const font = fetch(new URL('./Inter-Bold.ttf', import.meta.url)).then(
(res) => res.arrayBuffer()
)
export async function GET() {
const fontData = await font
return new ImageResponse(
(<div style={{ fontFamily: 'Inter' }}>Hello</div>),
{
width: 1200,
height: 630,
fonts: [{ name: 'Inter', data: fontData, weight: 700, style: 'normal' }],
}
)
}
For Google Fonts, fetch directly from the CDN or bundle the .ttf file.
Dynamic Content from URL Parameters
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const title = searchParams.get('title') ?? 'Default Title'
return new ImageResponse(
(<div style={{ display: 'flex', fontSize: 60 }}>{title}</div>),
{ width: 1200, height: 630 }
)
}
Images in OG
Use <img> with absolute URLs:
<img
src="https://example.com/avatar.png"
width={100}
height={100}
style={{ borderRadius: '50%' }}
/>
For local images, convert to base64 or use absolute deployment URLs.
Key Patterns
- Use
next/ogin Next.js projects — it re-exportsImageResponsewith built-in optimizations - Always set
runtime = 'edge'— Satori and@vercel/ogare designed for edge runtimes - Use
display: 'flex'everywhere — Satori defaults to flex layout, no block or grid support - Load fonts explicitly — no system fonts are available; bundle
.ttf/.wofffiles or fetch from CDN - Standard OG dimensions are 1200×630 — this is the most widely supported size
- Use convention files for automatic
<meta>tags —opengraph-image.tsxandtwitter-image.tsx - Inline styles only — Satori does not support external CSS or CSS-in-JS libraries