satori-cf 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
# satori-cf
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/satori-cf)
|
|
4
|
+
[](https://www.npmjs.com/package/satori-cf)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
> Lightweight, SVG-only Open Graph image generation for Cloudflare Workers
|
|
8
|
+
|
|
9
|
+
Generate dynamic OG images at the edge with minimal overhead. Built on [Satori](https://github.com/vercel/satori), optimized for Cloudflare Workers.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Tiny footprint** - ~90KB WASM (Yoga only), no resvg (~1.3MB saved)
|
|
14
|
+
- **SVG output** - Clean, scalable vectors that work everywhere
|
|
15
|
+
- **Batch processing** - Generate multiple images with single WASM init
|
|
16
|
+
- **Streaming API** - Handle large batches without memory issues
|
|
17
|
+
- **Google Fonts** - Load any font with built-in Cloudflare caching
|
|
18
|
+
- **Emoji support** - Twemoji, OpenMoji, Noto, and more
|
|
19
|
+
- **TypeScript** - Full type safety with Result pattern error handling
|
|
6
20
|
|
|
7
21
|
## Installation
|
|
8
22
|
|
|
9
23
|
```bash
|
|
10
24
|
npm install satori-cf
|
|
25
|
+
|
|
11
26
|
```
|
|
12
27
|
|
|
13
28
|
## Quick Start
|
|
@@ -18,9 +33,9 @@ import { generateOG, createSVGResponse } from "satori-cf";
|
|
|
18
33
|
export default {
|
|
19
34
|
async fetch(request: Request): Promise<Response> {
|
|
20
35
|
const result = await generateOG(
|
|
21
|
-
`<div style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; background: #667eea; color: white; font-size: 60px;">
|
|
36
|
+
`<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;">
|
|
22
37
|
Hello World
|
|
23
|
-
</div
|
|
38
|
+
</div>`,
|
|
24
39
|
);
|
|
25
40
|
|
|
26
41
|
if (result.ok) {
|
|
@@ -32,6 +47,246 @@ export default {
|
|
|
32
47
|
};
|
|
33
48
|
```
|
|
34
49
|
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### HTML String
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const result = await generateOG(`
|
|
56
|
+
<div style="display: flex; flex-direction: column; padding: 40px; background: #1a1a2e; color: white; width: 100%; height: 100%;">
|
|
57
|
+
<h1 style="font-size: 48px; margin: 0;">My Blog Post</h1>
|
|
58
|
+
<p style="font-size: 24px; color: #888;">Published on January 29, 2026</p>
|
|
59
|
+
</div>
|
|
60
|
+
`);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### React/JSX Element
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { generateOG } from "satori-cf";
|
|
67
|
+
|
|
68
|
+
const element = (
|
|
69
|
+
<div style={{ display: "flex", background: "#000", color: "#fff", width: "100%", height: "100%" }}>
|
|
70
|
+
<h1>Hello from JSX</h1>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const result = await generateOG(element);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### With Custom Fonts
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { generateOG, loadGoogleFont } from "satori-cf";
|
|
81
|
+
|
|
82
|
+
const fontData = await loadGoogleFont({
|
|
83
|
+
family: "Inter",
|
|
84
|
+
weight: 600,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const result = await generateOG(element, {
|
|
88
|
+
fonts: [
|
|
89
|
+
{
|
|
90
|
+
name: "Inter",
|
|
91
|
+
data: fontData,
|
|
92
|
+
weight: 600,
|
|
93
|
+
style: "normal",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### With Emojis
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const result = await generateOG(
|
|
103
|
+
`<div style="display: flex; font-size: 64px;">Hello 👋 World 🌍</div>`,
|
|
104
|
+
{ emoji: "twemoji" },
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Supported emoji sets: `twemoji`, `openmoji`, `blobmoji`, `noto`, `fluent`, `fluentFlat`
|
|
109
|
+
|
|
110
|
+
### Batch Generation
|
|
111
|
+
|
|
112
|
+
Generate multiple images efficiently with shared WASM initialization:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { generateOGBatch } from "satori-cf";
|
|
116
|
+
|
|
117
|
+
const items = [
|
|
118
|
+
{ element: '<div style="color: red;">Image 1</div>' },
|
|
119
|
+
{ element: '<div style="color: blue;">Image 2</div>' },
|
|
120
|
+
{ element: '<div style="color: green;">Image 3</div>' },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const results = await generateOGBatch(items, {
|
|
124
|
+
concurrency: 5,
|
|
125
|
+
failFast: false,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
for (const { index, result } of results) {
|
|
129
|
+
if (result.ok) {
|
|
130
|
+
console.log(`Image ${index}: ${result.value.length} bytes`);
|
|
131
|
+
} else {
|
|
132
|
+
console.error(`Image ${index} failed:`, result.error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Streaming Large Batches
|
|
138
|
+
|
|
139
|
+
For memory-efficient processing of large batches:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { OGStream } from "satori-cf";
|
|
143
|
+
|
|
144
|
+
const items = generateManyItems(); // Could be hundreds of items
|
|
145
|
+
|
|
146
|
+
for await (const { index, result } of OGStream(items, { prefetch: 3 })) {
|
|
147
|
+
if (result.ok) {
|
|
148
|
+
await saveToR2(result.value); // Save as you go
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### `generateOG(element, options?)`
|
|
156
|
+
|
|
157
|
+
Generate a single SVG image.
|
|
158
|
+
|
|
159
|
+
| Parameter | Type | Description |
|
|
160
|
+
| --------- | --------------------- | ---------------------------- |
|
|
161
|
+
| `element` | `string \| ReactNode` | HTML string or React element |
|
|
162
|
+
| `options` | `OGOptions` | Optional configuration |
|
|
163
|
+
|
|
164
|
+
Returns: `Promise<OGResult<string>>`
|
|
165
|
+
|
|
166
|
+
### `generateOGBatch(items, options?)`
|
|
167
|
+
|
|
168
|
+
Generate multiple images with amortized initialization.
|
|
169
|
+
|
|
170
|
+
| Parameter | Type | Description |
|
|
171
|
+
| --------- | -------------- | -------------------------------- |
|
|
172
|
+
| `items` | `BatchItem[]` | Array of `{ element, options? }` |
|
|
173
|
+
| `options` | `BatchOptions` | Batch configuration |
|
|
174
|
+
|
|
175
|
+
Returns: `Promise<BatchResult[]>`
|
|
176
|
+
|
|
177
|
+
### `OGStream(items, options?)`
|
|
178
|
+
|
|
179
|
+
AsyncGenerator for streaming large batches.
|
|
180
|
+
|
|
181
|
+
| Parameter | Type | Description |
|
|
182
|
+
| --------- | --------------------- | -------------------- |
|
|
183
|
+
| `items` | `Iterable<BatchItem>` | Items to process |
|
|
184
|
+
| `options` | `StreamOptions` | Stream configuration |
|
|
185
|
+
|
|
186
|
+
Returns: `AsyncGenerator<BatchResult>`
|
|
187
|
+
|
|
188
|
+
### `loadGoogleFont(options)`
|
|
189
|
+
|
|
190
|
+
Load a font from Google Fonts with caching.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const fontData = await loadGoogleFont({
|
|
194
|
+
family: "Roboto", // Font family name
|
|
195
|
+
weight: 400, // Font weight (optional)
|
|
196
|
+
text: "Hello", // Subset to specific characters (optional)
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Returns: `Promise<ArrayBuffer>`
|
|
201
|
+
|
|
202
|
+
### `createSVGResponse(svg, options?)`
|
|
203
|
+
|
|
204
|
+
Create an HTTP Response with proper SVG headers.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
return createSVGResponse(svg, {
|
|
208
|
+
status: 200,
|
|
209
|
+
headers: { "X-Custom": "header" },
|
|
210
|
+
debug: false, // Set true to disable caching
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Options
|
|
215
|
+
|
|
216
|
+
### OGOptions
|
|
217
|
+
|
|
218
|
+
| Option | Type | Default | Description |
|
|
219
|
+
| -------- | ----------- | ----------- | ------------------------ |
|
|
220
|
+
| `width` | `number` | `1200` | Image width in pixels |
|
|
221
|
+
| `height` | `number` | `630` | Image height in pixels |
|
|
222
|
+
| `fonts` | `Font[]` | Auto-loaded | Custom fonts array |
|
|
223
|
+
| `emoji` | `EmojiType` | - | Emoji provider |
|
|
224
|
+
| `debug` | `boolean` | `false` | Disable response caching |
|
|
225
|
+
|
|
226
|
+
### BatchOptions
|
|
227
|
+
|
|
228
|
+
Extends `OGOptions`:
|
|
229
|
+
|
|
230
|
+
| Option | Type | Default | Description |
|
|
231
|
+
| ------------- | --------- | ------- | ------------------------ |
|
|
232
|
+
| `concurrency` | `number` | `5` | Max parallel generations |
|
|
233
|
+
| `failFast` | `boolean` | `false` | Stop on first error |
|
|
234
|
+
|
|
235
|
+
### StreamOptions
|
|
236
|
+
|
|
237
|
+
Extends `OGOptions`:
|
|
238
|
+
|
|
239
|
+
| Option | Type | Default | Description |
|
|
240
|
+
| ---------- | -------- | ------- | ----------------------- |
|
|
241
|
+
| `prefetch` | `number` | `3` | Items to prefetch ahead |
|
|
242
|
+
|
|
243
|
+
## Result Pattern
|
|
244
|
+
|
|
245
|
+
All generators return a discriminated union for type-safe error handling:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
type OGResult<T> = { ok: true; value: T } | { ok: false; error: Error };
|
|
249
|
+
|
|
250
|
+
const result = await generateOG(element);
|
|
251
|
+
|
|
252
|
+
if (result.ok) {
|
|
253
|
+
// result.value is the SVG string
|
|
254
|
+
return createSVGResponse(result.value);
|
|
255
|
+
} else {
|
|
256
|
+
// result.error is the Error object
|
|
257
|
+
console.error(result.error.message);
|
|
258
|
+
return new Response("Failed", { status: 500 });
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Performance
|
|
263
|
+
|
|
264
|
+
Benchmarks on Cloudflare Workers (cold start):
|
|
265
|
+
|
|
266
|
+
| Operation | Time |
|
|
267
|
+
| ----------------------- | -------------------------- |
|
|
268
|
+
| First image (WASM init) | ~270ms |
|
|
269
|
+
| Subsequent images | ~90ms |
|
|
270
|
+
| Batch of 10 images | ~280ms total (~28ms/image) |
|
|
271
|
+
|
|
272
|
+
## Comparison
|
|
273
|
+
|
|
274
|
+
| Feature | satori-cf | workers-og | @vercel/og |
|
|
275
|
+
| --------- | ---------- | ---------- | ----------- |
|
|
276
|
+
| Output | SVG | PNG + SVG | PNG |
|
|
277
|
+
| WASM size | ~90KB | ~1.4MB | ~1.4MB |
|
|
278
|
+
| Platform | CF Workers | CF Workers | Vercel Edge |
|
|
279
|
+
| Batch API | Yes | No | No |
|
|
280
|
+
| Streaming | Yes | No | No |
|
|
281
|
+
|
|
282
|
+
## Credits
|
|
283
|
+
|
|
284
|
+
Built on the shoulders of giants:
|
|
285
|
+
|
|
286
|
+
- [Satori](https://github.com/vercel/satori) by Vercel - SVG generation engine
|
|
287
|
+
- [workers-og](https://github.com/kvnang/workers-og) by Kevin Ang - CF Workers patterns
|
|
288
|
+
- [@vercel/og](https://vercel.com/docs/functions/og-image-generation) - API inspiration
|
|
289
|
+
|
|
35
290
|
## License
|
|
36
291
|
|
|
37
|
-
MIT
|
|
292
|
+
MIT © [Pavle Dzuverovic](https://github.com/PavleDz)
|