satori-constraints

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.

15 stars

Best use case

satori-constraints is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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.

Teams using satori-constraints should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/satori-constraints/SKILL.md --create-dirs "https://raw.githubusercontent.com/shepherdjerred/monorepo/main/packages/dotfiles/dot_agents/skills/satori-constraints/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/satori-constraints/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How satori-constraints Compares

Feature / Agentsatori-constraintsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 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`, no `position: absolute` outside flex containers
- Every element with children must use `display: flex` explicitly
- Default flex direction is `row` (like React Native, unlike CSS default of `block`)

### 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` (only `flex` and `none`)
- `position` (`relative`, `absolute`)
- `top`, `right`, `bottom`, `left`
- `margin`, `padding` (all variants)
- `width`, `height`, `min-*`, `max-*`
- `flex`, `flex-grow`, `flex-shrink`, `flex-basis`
- `flex-direction`, `flex-wrap`
- `align-items`, `align-self`, `align-content`
- `justify-content`
- `gap`
- `overflow` (`hidden`, `visible`)

### Visual Properties

- `color`
- `background`, `background-color`, `background-image`
- `border`, `border-radius`
- `box-shadow`
- `opacity`

### Typography

- `font-family`, `font-size`, `font-weight`, `font-style`
- `line-height`
- `letter-spacing`
- `text-align`
- `text-decoration`
- `text-transform`
- `white-space`
- `word-break`
- `text-overflow`

### NOT Supported

- `z-index` - elements render in DOM order
- `transform` with 3D functions (`rotateX`, `translateZ`, etc.)
- `animation`, `transition`
- `cursor`
- `filter` (partial support)
- Advanced typography (kerning, ligatures, OpenType features)
- RTL text direction
- CSS Grid
- `float`
- `::before`, `::after` pseudo-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):

```typescript
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

1. **Define fonts globally** - don't create new font objects per render
2. **Use TTF/OTF on server** - faster to parse than WOFF
3. **Multiple weights** - pass each weight as separate font entry
4. **Fallback fonts** - provide fallbacks for missing characters

---

## Image Handling

### Image Requirements

```tsx
// 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}
/>
```

### Image Best Practices

1. **Use base64** - avoids extra network requests during rendering
2. **Always set dimensions** - `width` and `height` are required for external URLs
3. **Fetch and convert** - for dynamic images, fetch then convert to base64:

```typescript
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

```tsx
<div
  style={{
    backgroundImage: "url(data:image/png;base64,...)",
    backgroundSize: "cover",
    backgroundPosition: "center",
  }}
/>
```

---

## Emoji Support

Satori supports emoji through dynamic loading:

### Using `graphemeImages`

```typescript
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`

```typescript
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`:

```tsx
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`:

```tsx
<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

```typescript
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

1. **Lazy load** - import satori and resvg only when needed
2. **Cache fonts** - load fonts once at startup
3. **Reuse Resvg instances** - when generating many images
4. **Set appropriate dimensions** - larger images = longer render time

---

## Common Patterns

### OG Image Template

```tsx
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:

1. Add `border: '1px solid red'` to containers
2. Check that all containers have `display: 'flex'`
3. Verify flex direction (default is `row`)
4. Check for missing dimensions on images

---

## Sources

- [Satori GitHub Repository](https://github.com/vercel/satori)
- [Satori README](https://github.com/vercel/satori/blob/main/README.md)
- [Vercel OG Image Generation Docs](https://vercel.com/docs/og-image-generation)
- [Vercel Blog - Introducing OG Image Generation](https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images)
- [Satori NPM Package](https://www.npmjs.com/package/satori)
- [Satori DeepWiki](https://deepwiki.com/vercel/satori)
- [Next.js ImageResponse](https://nextjs.org/docs/app/api-reference/functions/image-response)
- [WOFF2 Support Discussion](https://github.com/vercel/satori/discussions/157)
- [Image Handling Issue](https://github.com/vercel/satori/issues/87)
- [Emoji Support Issue](https://github.com/vercel/satori/issues/1)
- [Gradient Bug](https://github.com/vercel/satori/issues/594)
- [Resvg-js GitHub](https://github.com/thx/resvg-js)
- [Generate Images with Satori and Resvg](https://dev.to/anasrin/generate-image-from-html-using-satori-and-resvg-46j6)
- [Implement Emojis in Satori](https://dev.to/opensauced/how-to-implement-emojis-in-vercelsatori-2no5)
- [Satori Fit Text](https://alan.norbauer.com/articles/satori-fit-text/)

Related Skills

toolkit-recall

15
from shepherdjerred/monorepo

Search past decisions, prior research, conversation history, monorepo docs, and fetched web pages. Use toolkit recall search to find context from previous work, and toolkit fetch to save web pages for future search.

zod-patterns

15
from shepherdjerred/monorepo

Zod schema validation, type-safe development, and strict TypeScript patterns. When user works with Zod, validates data, creates schemas, handles form validation, mentions z.object/z.string patterns, needs runtime validation, type-safe code, or strict TypeScript configuration.

zellij-helper

15
from shepherdjerred/monorepo

Zellij terminal multiplexer for session management, layouts, and pane operations When user mentions Zellij, terminal multiplexer, zellij commands, sessions, panes, layouts, or tabs

worktree-workflow

15
from shepherdjerred/monorepo

Git worktree workflow for isolated feature development and PR creation When user starts new work, needs to switch contexts, or wants parallel development

vite-react-helper

15
from shepherdjerred/monorepo

Vite + React for fast modern web development - build config, HMR, hooks, state management, and performance patterns When user works with Vite, React, creates components, manages state, uses hooks, or configures Vite builds

version-management

15
from shepherdjerred/monorepo

Use when asking about version management, Renovate annotations, versions.ts patterns, or pinning image/chart versions.

typst-authoring

15
from shepherdjerred/monorepo

This skill should be used when the user asks to "write Typst", "create a Typst document", "format in Typst", "convert to Typst", "Typst syntax", "Typst template", "Typst math", "Typst table", or works with .typ files. Provides comprehensive Typst markup, scripting, math, layout, and styling reference for authoring documents. Also use proactively when generating .typ output files (e.g., in deep-research reports).

typescript-helper

15
from shepherdjerred/monorepo

TypeScript development guidance for type systems and tooling When user works with .ts or .tsx files, mentions TypeScript, or encounters type errors

torvalds-deployment

15
from shepherdjerred/monorepo

Use when asking about adding services, createXxxDeployment patterns, or homelab deployment conventions. Services use per-namespace charts.

terraform-helper

15
from shepherdjerred/monorepo

Terraform and OpenTofu infrastructure as code - HCL, providers, modules, state management, and CLI operations When user works with .tf files, mentions Terraform, OpenTofu, tofu, HCL, infrastructure as code, or tf commands

terminal-concepts

15
from shepherdjerred/monorepo

Comprehensive guide for building CLI and TUI applications - terminal internals, design principles, and battle-tested patterns When building CLI/TUI apps, implementing argument parsing, handling terminal input/output, escape codes, buffering, signals, or asking about terminal development concepts

talos-helper

15
from shepherdjerred/monorepo

Talos Linux cluster administration using talosctl When user mentions Talos, talosctl, or Talos cluster operations