@ogify/core 0.1.4 → 0.5.0

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 CHANGED
@@ -1,97 +1,113 @@
1
- # OGify - Beautiful Dynamic OG Images in Seconds
1
+ # OGify - Generate beautiful OG images in minutes
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/%40ogify%2Fcore.svg)](https://badge.fury.io/js/%40ogify%2Fcore)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
6
 
7
- Generate stunning Open Graph images in seconds, not hours. OGify eliminates the complexity of OG image generation with zero-config font loading, automatic caching, and production-ready templates.
7
+ Zero-config dynamic Open Graph images for Next.js, Nuxt, Remix, and more. Just copy & paste the production-ready templates.
8
+
9
+ ![OGify](../../apps/examples/outputs/aligned-aligned-ltr.png)
8
10
 
9
11
  ## ⚡ Why OGify?
10
12
 
11
- - 🔌 **Zero-config**: Works out of the box with Next.js, Remix, Astro, etc.
12
- - 🎨 **No font management**: Just specify a Google Font name - no downloads, no font files, no hassle.
13
- - 📦 **No asset pipeline**: Emojis load dynamically from CDNs.
14
- - 🖼️ **Rich templates**: OGify provides a set of production-ready templates with zero configuration.
15
- - ⚡ **Smart caching**: OGify automatically caches fonts, emojis, and generated images - no configuration required.
16
- - 🛡️ **Type-safe**: Full TypeScript support catches errors before runtime.
13
+ - 🔌 **Zero-config**: Works out of the box with Next.js, Remix, Nuxt, and more.
14
+ - 🔤 **Hassle-free assets**: Just specify a Google Font name, Emoji provider - no downloads, no font files, no hassle.
15
+ - 🎨 **Flexible Customization**: Intuitive API & Tailwind-like syntax helps building eye-catching templates faster.
16
+ - 🖼️ **Production-ready templates**: OGify provides a set of production-ready templates with zero configuration.
17
+ - ⚡ **Smart caching**: Automatically caches fonts, emojis, and generated images - no configuration required.
18
+ - 🌍 **RTL Support**: Built-in support for Right-to-Left languages like Arabic, Hebrew, and Persian.
17
19
 
18
20
  ## 📦 Installation
19
21
 
20
22
  ```bash
21
- pnpm add @ogify/core
23
+ pnpm add @ogify/core @ogify/templates
24
+ ```
25
+
26
+ or
27
+
28
+ ```bash
29
+ npm install @ogify/core @ogify/templates
30
+ ```
31
+
32
+ or
33
+
34
+ ```bash
35
+ yarn add @ogify/core @ogify/templates
22
36
  ```
23
37
 
24
38
  ## 🚀 Quick Start
25
39
 
26
- ### 1. Define a Template
40
+ ### 1. Choose a Template
27
41
 
28
- Create a template using `defineTemplate` with an HTML renderer function:
42
+ OGify comes with a collection of beautiful, production-ready templates. Check out [our gallery](https://ogify.dev/templates) for more.
29
43
 
30
44
  ```typescript
31
- import { defineTemplate, OgTemplateOptions } from '@ogify/core';
32
-
33
- const blogTemplate = defineTemplate({
34
- id: 'blog-post',
35
- name: 'Blog Post',
36
- description: 'Template for blog post OG images',
37
- fonts: [
38
- { name: 'Inter', weight: 700 },
39
- { name: 'Inter', weight: 400 }
40
- ],
41
- renderer: ({ params }: OgTemplateOptions) => {
42
- return `
43
- <div style="display: flex; flex-direction: column; width: 100%; height: 100%; background: white; padding: 40px;">
44
- <h1 style="font-size: 60px; font-weight: 700; color: #000;">
45
- ${params.title}
46
- </h1>
47
- <p style="font-size: 30px; color: #666;">
48
- ${params.description}
49
- </p>
50
- </div>
51
- `;
52
- },
53
- });
45
+ import template from '@ogify/templates/basic';
46
+ import type { TemplateParams } from '@ogify/templates/basic';
54
47
  ```
55
48
 
56
49
  ### 2. Create a Renderer
57
50
 
58
- Create a renderer instance and register your templates:
51
+ Create a renderer instance and register your template:
59
52
 
60
53
  ```typescript
61
- import { createTemplateRenderer } from '@ogify/core';
62
-
63
- const renderer = createTemplateRenderer({
64
- templates: [blogTemplate],
65
- defaultParams: {
66
- brand: 'My Company'
54
+ import { createRenderer } from '@ogify/core';
55
+ import template from '@ogify/templates/basic';
56
+ import type { TemplateParams } from '@ogify/templates/basic';
57
+
58
+ const renderer = createRenderer<{ basic: TemplateParams }>({
59
+ templates: { basic: template },
60
+ sharedParams: {
61
+ brandLogo: 'https://ogify.dev/logo.svg',
62
+ brandName: 'OGify',
67
63
  }
68
64
  });
69
65
  ```
70
66
 
71
67
  ### 3. Generate an Image
72
68
 
73
- Render a template to a PNG buffer:
69
+ Render the template to a PNG buffer:
74
70
 
75
71
  ```typescript
76
72
  // Basic usage (1200x630)
77
- const imageBuffer = await renderer.renderToImage('blog-post', {
73
+ const imageBuffer = await renderer.renderToImage('basic', {
78
74
  title: 'Hello World',
79
- description: 'My first OG image'
75
+ subtitle: 'My first OG image',
76
+ layout: 'centered', // aligned | centered | split
77
+ cta: 'Read More',
78
+ primaryColor: '#000000',
80
79
  });
81
80
 
82
81
  // Custom dimensions for Twitter (1200x675)
83
- const twitterImage = await renderer.renderToImage('blog-post', {
84
- title: 'Hello World'
82
+ const twitterImage = await renderer.renderToImage('basic', {
83
+ title: 'Hello World',
84
+ subtitle: 'My first OG image',
85
+ layout: 'split',
85
86
  }, {
86
87
  width: 1200,
87
88
  height: 675
88
89
  });
90
+
91
+ // RTL Support (Arabic/Hebrew/Persian)
92
+ const rtlImage = await renderer.renderToImage('basic', {
93
+ title: 'مرحبا بالعالم',
94
+ subtitle: 'هذه أول صورة OG لي',
95
+ layout: 'aligned',
96
+ }, {
97
+ isRTL: true
98
+ });
89
99
  ```
90
100
 
91
101
  **That's it!** You're ready for production. No font files to download, no build configuration, no asset pipeline.
92
102
 
93
103
  ## 🚀 Time-Saving Features
94
104
 
105
+ ### **Rich Templates**
106
+
107
+ Access a growing library of beautiful, production-ready templates. [Browse Templates](https://ogify.dev/templates)
108
+
109
+ **Time saved**: Hours of design and implementation time
110
+
95
111
  ### **Zero-Config Google Fonts**
96
112
 
97
113
  Traditional approach (manual setup):
@@ -99,23 +115,24 @@ Traditional approach (manual setup):
99
115
  ```typescript
100
116
  // ❌ Manual: Download fonts, manage files, configure paths
101
117
  import fs from 'fs';
102
- const fontData = fs.readFileSync('./fonts/Inter-Bold.ttf');
103
- const font2Data = fs.readFileSync('./fonts/Inter-Regular.ttf');
118
+
119
+ const fontData = fs.readFileSync('./fonts/Inter-Regular.ttf');
120
+ const font2Data = fs.readFileSync('./fonts/Inter-Bold.ttf');
104
121
 
105
122
  const fonts = [
106
- { name: 'Inter', data: fontData, weight: 700 },
107
- { name: 'Inter', data: font2Data, weight: 400 }
123
+ { name: 'Inter', data: fontData, weight: 400 },
124
+ { name: 'Inter', data: font2Data, weight: 700 }
108
125
  ];
109
126
  ```
110
127
 
111
- OGify approach (zero config):
128
+ OGify approach (zero-config):
112
129
 
113
130
  ```typescript
114
131
  // ✅ OGify: Just specify the font name - we handle the rest
115
132
  const template = defineTemplate({
116
133
  fonts: [
117
- { name: 'Inter', weight: 700 }, // Automatically loaded from Google Fonts
118
- { name: 'Inter', weight: 400 }
134
+ { name: 'Inter', weight: 400 }, // Automatically loaded from Google Fonts
135
+ { name: 'Inter', weight: 700 }
119
136
  ],
120
137
  // ...
121
138
  });
@@ -127,13 +144,13 @@ const template = defineTemplate({
127
144
 
128
145
  ```typescript
129
146
  // First render: Downloads fonts from Google (~500ms)
130
- await renderer.renderToImage('blog-post', { title: 'Post 1' });
147
+ await renderer.renderToImage('basic', { title: 'Post 1', subtitle: '...' });
131
148
 
132
149
  // Second render: Uses cached fonts (~50ms) - 10x faster! ⚡
133
- await renderer.renderToImage('blog-post', { title: 'Post 2' });
150
+ await renderer.renderToImage('basic', { title: 'Post 2', subtitle: '...' });
134
151
 
135
152
  // All subsequent renders: Lightning fast
136
- await renderer.renderToImage('blog-post', { title: 'Post 3' });
153
+ await renderer.renderToImage('basic', { title: 'Post 3', subtitle: '...' });
137
154
  ```
138
155
 
139
156
  **Performance**: 10x faster after first render, no configuration needed
@@ -152,6 +169,33 @@ renderer: ({ params }) => `
152
169
 
153
170
  **Time saved**: No emoji sprite sheets, no asset management, no build step
154
171
 
172
+ ## 🛠️ Creating Custom Templates
173
+
174
+ If you want to build your own templates from scratch, you can use `defineTemplate`.
175
+
176
+ ```typescript
177
+ import { defineTemplate, OgTemplateOptions } from '@ogify/core';
178
+
179
+ const blogTemplate = defineTemplate({
180
+ fonts: [
181
+ { name: 'Inter', weight: 400 },
182
+ { name: 'Inter', weight: 700 }
183
+ ],
184
+ renderer: ({ params }: OgTemplateOptions) => {
185
+ return `
186
+ <div style="display: flex; flex-direction: column; width: 100%; height: 100%; background: white; padding: 40px;">
187
+ <h1 style="font-size: 60px; font-weight: 700; color: #000;">
188
+ ${params.title}
189
+ </h1>
190
+ <p style="font-size: 30px; color: #666;">
191
+ ${params.description}
192
+ </p>
193
+ </div>
194
+ `;
195
+ },
196
+ });
197
+ ```
198
+
155
199
  ## 📚 Advanced Usage
156
200
 
157
201
  ### Custom Fonts
@@ -160,9 +204,6 @@ Load fonts from Google Fonts, custom URLs, or embedded data:
160
204
 
161
205
  ```typescript
162
206
  const template = defineTemplate({
163
- id: 'custom-fonts',
164
- name: 'Custom Fonts Template',
165
- description: 'Template with custom fonts',
166
207
  fonts: [
167
208
  // Google Fonts (automatic)
168
209
  { name: 'Roboto', weight: 400 },
@@ -183,11 +224,8 @@ Choose from multiple emoji providers:
183
224
 
184
225
  ```typescript
185
226
  const template = defineTemplate({
186
- id: 'emoji-template',
187
- name: 'Emoji Template',
188
- description: 'Template with emoji support',
189
227
  fonts: [{ name: 'Inter', weight: 400 }],
190
- emojiProvider: 'twemoji', // or 'fluent', 'noto', 'openmoji', etc.
228
+ emojiProvider: 'twemoji', // 'fluent' | 'fluentFlat' | 'noto' | 'blobmoji' | 'openmoji'
191
229
  renderer: ({ params }) => `
192
230
  <div>
193
231
  <h1>${params.title}</h1>
@@ -202,8 +240,8 @@ const template = defineTemplate({
202
240
  Add custom logic before and after rendering:
203
241
 
204
242
  ```typescript
205
- const renderer = createTemplateRenderer({
206
- templates: [blogTemplate],
243
+ const renderer = createRenderer({
244
+ templates: { 'blog-post': blogTemplate },
207
245
  beforeRender: async (templateId, params) => {
208
246
  console.log(`Rendering ${templateId}`, params);
209
247
  // Log analytics, validate params, etc.
@@ -215,16 +253,49 @@ const renderer = createTemplateRenderer({
215
253
  });
216
254
  ```
217
255
 
256
+ ### Caching Configuration
257
+
258
+ Configure caching strategy for fonts and emojis/icons:
259
+
260
+ ```typescript
261
+ // Memory Cache (default)
262
+ const memoryRenderer = createRenderer({
263
+ templates: { 'blog-post': blogTemplate },
264
+ cache: {
265
+ type: 'memory',
266
+ ttl: 3600000, // 1 hour
267
+ max: 100 // max items
268
+ }
269
+ });
270
+
271
+ // Filesystem Cache
272
+ const fsRenderer = createRenderer({
273
+ templates: { 'blog-post': blogTemplate },
274
+ cache: {
275
+ type: 'filesystem',
276
+ dir: './.ogify-cache', // cache directory
277
+ ttl: 3600000, // 1 hour
278
+ max: 100 // max items
279
+ }
280
+ });
281
+ ```
282
+
218
283
  ### Platform-Specific Dimensions
219
284
 
220
285
  Generate images for different platforms:
221
286
 
222
287
  ```typescript
223
288
  // Facebook/LinkedIn (1200x630)
224
- const facebookImage = await renderer.renderToImage('blog-post', params);
289
+ const facebookImage = await renderer.renderToImage('basic', {
290
+ title: 'Hello World',
291
+ subtitle: 'My first OG image',
292
+ });
225
293
 
226
294
  // Twitter (1200x675)
227
- const twitterImage = await renderer.renderToImage('blog-post', params, {
295
+ const twitterImage = await renderer.renderToImage('basic', {
296
+ title: 'Hello World',
297
+ subtitle: 'My first OG image',
298
+ }, {
228
299
  width: 1200,
229
300
  height: 675
230
301
  });
@@ -234,7 +305,7 @@ const twitterImage = await renderer.renderToImage('blog-post', params, {
234
305
 
235
306
  ### Supported CSS Properties
236
307
 
237
- Templates support a subset of CSS properties via Satori:
308
+ Templates support a subset of CSS properties via Satori. See [Satori CSS](https://github.com/vercel/satori?tab=readme-ov-file#css) for more details.
238
309
 
239
310
  - **Layout**: `display: flex`, `flexDirection`, `alignItems`, `justifyContent`
240
311
  - **Spacing**: `padding`, `margin`, `gap`
@@ -242,6 +313,7 @@ Templates support a subset of CSS properties via Satori:
242
313
  - **Background**: `backgroundColor`, `backgroundImage`
243
314
  - **Border**: `border`, `borderRadius`
244
315
  - **Size**: `width`, `height`, `maxWidth`, `maxHeight`
316
+ - ...
245
317
 
246
318
  ### Tailwind-like Utilities
247
319
 
@@ -264,23 +336,21 @@ Defines a new OG template.
264
336
 
265
337
  **Parameters:**
266
338
 
267
- - `id` (string): Unique identifier
268
- - `name` (string): Human-readable name
269
- - `description` (string): Template description
270
339
  - `renderer` (function): Function that returns HTML string
271
340
  - `fonts` (array): Array of font configurations
272
341
  - `emojiProvider` (optional): Emoji provider to use
273
342
 
274
343
  **Returns:** `OgTemplate`
275
344
 
276
- ### `createTemplateRenderer(config)`
345
+ ### `createRenderer(config)`
277
346
 
278
347
  Creates a new template renderer instance.
279
348
 
280
349
  **Parameters:**
281
350
 
282
- - `templates` (array): Array of template definitions
283
- - `defaultParams` (optional): Default parameters for all templates
351
+ - `templates` (object): Map of template definitions keyed by ID
352
+ - `sharedParams` (optional): Default parameters for all templates
353
+ - `cache` (optional): Cache configuration object
284
354
  - `beforeRender` (optional): Hook called before rendering
285
355
  - `afterRender` (optional): Hook called after rendering
286
356
 
@@ -293,40 +363,48 @@ Renders a template to a PNG buffer.
293
363
  **Parameters:**
294
364
 
295
365
  - `templateId` (string): ID of the template to render
296
- - `params` (object): Parameters to pass to the template
366
+ - `params` (object | function): Parameters (or function returning params) to pass to the template
297
367
  - `options` (optional): Rendering options
298
368
  - `width` (number): Image width (default: 1200)
299
369
  - `height` (number): Image height (default: 630)
370
+ - `isRTL` (boolean): Enable Right-to-Left text direction (default: false)
300
371
 
301
372
  **Returns:** `Promise<Buffer>`
302
373
 
303
- ### `renderer.getTemplate(id)`
374
+ ### RTL Support
304
375
 
305
- Retrieves a template by ID.
376
+ OGify supports Right-to-Left (RTL) languages via the `isRTL` option.
306
377
 
307
- **Returns:** `OgTemplate | undefined`
378
+ ```typescript
379
+ const image = await renderer.renderToImage('basic', {
380
+ title: 'مرحبا بالعالم', // Arabic: Hello World
381
+ subtitle: 'هذه أول صورة OG لي',
382
+ }, {
383
+ isRTL: true
384
+ });
385
+ ```
308
386
 
309
- ### `renderer.getTemplateIds()`
387
+ ### `renderer.getTemplate(id)`
310
388
 
311
- Gets all registered template IDs.
389
+ Retrieves a template by ID.
312
390
 
313
- **Returns:** `string[]`
391
+ **Returns:** `OgTemplate | undefined`
314
392
 
315
393
  ## ⚡ Performance & Production
316
394
 
317
395
  ### **Automatic Caching (Zero Config)**
318
396
 
319
- OGify automatically caches fonts and emojis in memory - no configuration required:
397
+ OGify automatically caches fonts, emojis, and generated images in memory - no configuration required
320
398
 
321
399
  ```typescript
322
400
  // First render: Downloads fonts from Google Fonts (~500ms)
323
- await renderer.renderToImage('blog-post', { title: 'Post 1' });
401
+ await renderer.renderToImage('basic', { title: 'Post 1', subtitle: '...' });
324
402
 
325
403
  // Second render: Uses cached fonts (~50ms) - 10x faster! ⚡
326
- await renderer.renderToImage('blog-post', { title: 'Post 2' });
404
+ await renderer.renderToImage('basic', { title: 'Post 2', subtitle: '...' });
327
405
 
328
406
  // Third+ renders: Lightning fast from cache
329
- await renderer.renderToImage('blog-post', { title: 'Post 3' });
407
+ await renderer.renderToImage('basic', { title: 'Post 3', subtitle: '...' });
330
408
  ```
331
409
 
332
410
  ## 📋 Changelog
@@ -347,4 +425,5 @@ Built on top of:
347
425
 
348
426
  - [satori](https://github.com/vercel/satori) - SVG generation
349
427
  - [satori-html](https://github.com/vercel/satori-html) - HTML to VDOM conversion
350
- - [resvg-js](https://github.com/thx/resvg-js) - PNG conversion
428
+ - [@resvg/resvg-js](https://github.com/thx/resvg-js) - SVG to PNG conversion
429
+ - [lru-cache](https://github.com/isaacs/node-lru-cache) - LRU cache