npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

satori-cf

v0.0.3

Published

SVG-only, batch-optimized Open Graph image generation for Cloudflare Workers

Downloads

431

Readme

satori-cf

npm version npm downloads License: MIT

Lightweight, SVG-only Open Graph image generation for Cloudflare Workers

Generate dynamic OG images at the edge with minimal overhead. Built on Satori, optimized for Cloudflare Workers.

Features

  • Tiny footprint - ~90KB WASM (Yoga only), no resvg (~1.3MB saved)
  • SVG output - Clean, scalable vectors that work everywhere
  • Batch processing - Generate multiple images with single WASM init
  • Streaming API - Handle large batches without memory issues
  • Google Fonts - Load any font with built-in Cloudflare caching
  • Emoji support - Twemoji, OpenMoji, Noto, and more
  • TypeScript - Full type safety with Result pattern error handling

Installation

npm install satori-cf

Quick Start

import { generateOG, createSVGResponse } from "satori-cf";

export default {
  async fetch(request: Request): Promise<Response> {
    const result = await generateOG(
      `<div style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 60px;">
        Hello World
      </div>`,
    );

    if (result.ok) {
      return createSVGResponse(result.value);
    }

    return new Response(result.error.message, { status: 500 });
  },
};

Usage

HTML String

const result = await generateOG(`
  <div style="display: flex; flex-direction: column; padding: 40px; background: #1a1a2e; color: white; width: 100%; height: 100%;">
    <h1 style="font-size: 48px; margin: 0;">My Blog Post</h1>
    <p style="font-size: 24px; color: #888;">Published on January 29, 2026</p>
  </div>
`);

React/JSX Element

import { generateOG } from "satori-cf";

const element = (
  <div style={{ display: "flex", background: "#000", color: "#fff", width: "100%", height: "100%" }}>
    <h1>Hello from JSX</h1>
  </div>
);

const result = await generateOG(element);

With Custom Fonts

import { generateOG, loadGoogleFont } from "satori-cf";

const fontData = await loadGoogleFont({
  family: "Inter",
  weight: 600,
});

const result = await generateOG(element, {
  fonts: [
    {
      name: "Inter",
      data: fontData,
      weight: 600,
      style: "normal",
    },
  ],
});

With Emojis

const result = await generateOG(
  `<div style="display: flex; font-size: 64px;">Hello 👋 World 🌍</div>`,
  { emoji: "twemoji" },
);

Supported emoji sets: twemoji, openmoji, blobmoji, noto, fluent, fluentFlat

Batch Generation

Generate multiple images efficiently with shared WASM initialization:

import { generateOGBatch } from "satori-cf";

const items = [
  { element: '<div style="color: red;">Image 1</div>' },
  { element: '<div style="color: blue;">Image 2</div>' },
  { element: '<div style="color: green;">Image 3</div>' },
];

const results = await generateOGBatch(items, {
  concurrency: 5,
  failFast: false,
});

for (const { index, result } of results) {
  if (result.ok) {
    console.log(`Image ${index}: ${result.value.length} bytes`);
  } else {
    console.error(`Image ${index} failed:`, result.error);
  }
}

Streaming Large Batches

For memory-efficient processing of large batches:

import { OGStream } from "satori-cf";

const items = generateManyItems(); // Could be hundreds of items

for await (const { index, result } of OGStream(items, { prefetch: 3 })) {
  if (result.ok) {
    await saveToR2(result.value); // Save as you go
  }
}

API Reference

generateOG(element, options?)

Generate a single SVG image.

| Parameter | Type | Description | | --------- | --------------------- | ---------------------------- | | element | string \| ReactNode | HTML string or React element | | options | OGOptions | Optional configuration |

Returns: Promise<OGResult<string>>

generateOGBatch(items, options?)

Generate multiple images with amortized initialization.

| Parameter | Type | Description | | --------- | -------------- | -------------------------------- | | items | BatchItem[] | Array of { element, options? } | | options | BatchOptions | Batch configuration |

Returns: Promise<BatchResult[]>

OGStream(items, options?)

AsyncGenerator for streaming large batches.

| Parameter | Type | Description | | --------- | --------------------- | -------------------- | | items | Iterable<BatchItem> | Items to process | | options | StreamOptions | Stream configuration |

Returns: AsyncGenerator<BatchResult>

loadGoogleFont(options)

Load a font from Google Fonts with caching.

const fontData = await loadGoogleFont({
  family: "Roboto", // Font family name
  weight: 400, // Font weight (optional)
  text: "Hello", // Subset to specific characters (optional)
});

Returns: Promise<ArrayBuffer>

createSVGResponse(svg, options?)

Create an HTTP Response with proper SVG headers.

return createSVGResponse(svg, {
  status: 200,
  headers: { "X-Custom": "header" },
  debug: false, // Set true to disable caching
});

Options

OGOptions

| Option | Type | Default | Description | | -------- | ----------- | ----------- | ------------------------ | | width | number | 1200 | Image width in pixels | | height | number | 630 | Image height in pixels | | fonts | Font[] | Auto-loaded | Custom fonts array | | emoji | EmojiType | - | Emoji provider | | debug | boolean | false | Disable response caching |

BatchOptions

Extends OGOptions:

| Option | Type | Default | Description | | ------------- | --------- | ------- | ------------------------ | | concurrency | number | 5 | Max parallel generations | | failFast | boolean | false | Stop on first error |

StreamOptions

Extends OGOptions:

| Option | Type | Default | Description | | ---------- | -------- | ------- | ----------------------- | | prefetch | number | 3 | Items to prefetch ahead |

Result Pattern

All generators return a discriminated union for type-safe error handling:

type OGResult<T> = { ok: true; value: T } | { ok: false; error: Error };

const result = await generateOG(element);

if (result.ok) {
  // result.value is the SVG string
  return createSVGResponse(result.value);
} else {
  // result.error is the Error object
  console.error(result.error.message);
  return new Response("Failed", { status: 500 });
}

Performance

Benchmarks on Cloudflare Workers (cold start):

| Operation | Time | | ----------------------- | -------------------------- | | First image (WASM init) | ~270ms | | Subsequent images | ~90ms | | Batch of 10 images | ~280ms total (~28ms/image) |

Comparison

| Feature | satori-cf | workers-og | @vercel/og | | --------- | ---------- | ---------- | ----------- | | Output | SVG | PNG + SVG | PNG | | WASM size | ~90KB | ~1.4MB | ~1.4MB | | Platform | CF Workers | CF Workers | Vercel Edge | | Batch API | Yes | No | No | | Streaming | Yes | No | No |

Credits

Built on the shoulders of giants:

License

MIT © Pavle Dzuverovic