satori

SKILL.md

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: fixed or sticky
  • 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

  1. Use next/og in Next.js projects — it re-exports ImageResponse with built-in optimizations
  2. Always set runtime = 'edge' — Satori and @vercel/og are designed for edge runtimes
  3. Use display: 'flex' everywhere — Satori defaults to flex layout, no block or grid support
  4. Load fonts explicitly — no system fonts are available; bundle .ttf/.woff files or fetch from CDN
  5. Standard OG dimensions are 1200×630 — this is the most widely supported size
  6. Use convention files for automatic <meta> tagsopengraph-image.tsx and twitter-image.tsx
  7. Inline styles only — Satori does not support external CSS or CSS-in-JS libraries

Official Resources

Weekly Installs
4
GitHub Stars
7
First Seen
9 days ago
Installed on
opencode4
mcpjam3
claude-code3
junie3
windsurf3
zencoder3