og-card-generator
v1.0.0
Published
Generate branded Open Graph / social share images (1200x630) that render correctly on serverless headless Chromium, by embedding the font as base64. No system-font tofu.
Maintainers
Readme
og-card-generator
Generate branded Open Graph / social share images (1200x630) for every page, that actually render on serverless.
When you generate OG images at build time or on the edge with headless Chromium (Vercel, AWS Lambda, Cloudflare), the runtime ships no system fonts — so your text renders as empty boxes (tofu) even though it looked perfect locally. That single gotcha breaks most home-grown OG-image setups.
og-card-generator side-steps it by embedding the font as base64 directly in the card, so it renders identically everywhere, with no network fetch and no reliance on system fonts.

- Zero-dependency core (
cardHtmlreturns an HTML string you can render however you like) - Optional one-call PNG via puppeteer (
renderToPng) - Auto-sizes the headline, fully themeable (colors, accent, fonts)
- Works at build time (prerender), on the edge, or in a serverless function
Install
npm install og-card-generatorQuick start
import { cardHtml, renderToPng, loadFontBase64 } from 'og-card-generator';
const fontBase64 = loadFontBase64('./fonts/inter-600.woff2');
// 1) Just the HTML (zero deps) — screenshot it with whatever you already use.
const html = cardHtml({
title: 'How much does a website cost?',
eyebrow: 'Acme Studio',
footer: 'acme.com/pricing',
fontBase64,
});
// 2) Or render straight to a PNG buffer (needs: npm i puppeteer)
const png = await renderToPng({
title: 'How much does a website cost?',
eyebrow: 'Acme Studio',
footer: 'acme.com/pricing',
fontBase64,
accent: '#7c5cff',
});
import { writeFileSync } from 'node:fs';
writeFileSync('og.png', png);Then point each page's meta at the image:
<meta property="og:image" content="https://acme.com/og/pricing.png" />
<meta name="twitter:image" content="https://acme.com/og/pricing.png" />Serverless rendering (Vercel / Lambda)
Pass your own launcher so you can use puppeteer-core + @sparticuz/chromium:
import chromium from '@sparticuz/chromium';
import puppeteer from 'puppeteer-core';
const png = await renderToPng({
title: 'Built for your market',
fontBase64,
launch: () => puppeteer.launch({
args: chromium.args,
executablePath: chromium.executablePath(),
headless: chromium.headless,
}),
});Because the font is embedded, the font-less serverless Chromium still renders the text correctly.
Options
| Option | Default | Notes |
|--------------|-----------------------|----------------------------------------------------|
| title | placeholder | Headline. Auto-sizes by length. |
| eyebrow | YOUR BRAND | Small label above the title. |
| footer | yourdomain.com | Bottom-left line (e.g. the page URL). |
| fontBase64 | '' | base64 of a woff2/ttf. Use loadFontBase64(path). |
| fontFamily | CardFont | CSS family name for the embedded font. |
| width | 1200 | Card width. |
| height | 630 | Card height. |
| background | #0d1f22 | Card background. |
| accent | #93B7BE | Bar, eyebrow, footer color. |
| text | #F1FFFA | Title color. |
| titleSize | auto | Override the auto title size (px). |
| scale | 1 | renderToPng deviceScaleFactor (2 = retina). |
| launch | puppeteer default | renderToPng custom browser launcher. |
Run the example:
npm i puppeteer
node example/generate.mjs # writes example/out.pngFonts
The example/ folder includes Inter (SIL Open Font License 1.1) for a runnable demo. Bring any woff2/ttf you like for your own brand; only you decide what font ships.
License
MIT for the code. See LICENSE. The bundled Inter font is under the SIL OFL 1.1.
Built and maintained by Clap Digital, a full-stack web and AI agency. We use this to give every page on our site its own branded share card. If it saves you time, a star is appreciated.
