Web & UI pack
Claude Skill
OG Image Generator
Generates Open Graph / social card images from title + author + brand. Templated via Satori or Puppeteer.
What it does
Produces 1200×630 OG images for social link previews (Twitter/X, LinkedIn, Slack, iMessage). Templated by title, author, optional subtitle, and brand. Two implementation paths: Satori (HTML→SVG→PNG, fast, runs at edge) or Puppeteer (HTML→screenshot, fully featured). Includes the API route to generate on demand.
When to use
- ✓You ship blog posts or marketing pages and need per-page OG images automatically
- ✓Existing OG image is a single static logo PNG — every link looks identical
- ✓You're using Next.js / Vercel and want \`@vercel/og\` or Satori OG generation
When not to use
- ✗You have a designer who hand-makes hero images per post — they'll do it better
- ✗You only ship 5 pages a year — design them in Figma, export, done
Install
Download the .zip, then unzip into your Claude skills folder.
mkdir -p ~/.claude/skills
unzip ~/Downloads/og-image-generator.zip -d ~/.claude/skills/
# Restart Claude Code session.
# Skill is now available — Claude will use it when relevant.SKILL.md
SKILL.md
---
name: og-image-generator
description: Use when generating Open Graph / social card images from a template — title + author + brand. Triggers on "OG image", "social card", "open graph image", "Twitter card image".
---
# OG Image Generator
OG images are the link previews on Twitter/X, LinkedIn, Slack, iMessage, Discord. A bad one (or the same one for every page) is wasted real estate. Templated, per-page OG images compound link sharing.
## Required inputs
1. **Brand** — logo (SVG ideally), accent color, brand fonts (or Google Fonts you'll embed)
2. **Template variables** — what's dynamic per page (title, author, date, category, ...)
3. **Implementation** — Satori (Next.js / edge / Cloudflare Workers), Puppeteer (Node server), or one-off (Figma export)
4. **Output format** — PNG (universal). 1200x630 for Twitter Large Card and LinkedIn.
## Output format and dimensions
- **Standard: 1200x630px** — works for Twitter Large Card, LinkedIn, Open Graph default
- Twitter "summary" small card: 1200x600 also works
- iMessage: uses OG, fine with 1200x630
- 2:1 ratio is the safe default. Don't go square (Slack will crop weirdly).
## Path 1: Satori (recommended for static sites)
`@vercel/og` (Satori under the hood) — JSX → SVG → PNG. No headless browser, runs at edge.
```
// app/api/og/route.tsx
import { ImageResponse } from 'next/og';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const title = searchParams.get('title') ?? 'Untitled';
const author = searchParams.get('author') ?? '';
return new ImageResponse(
(
<div style={{
display: 'flex', flexDirection: 'column',
width: '100%', height: '100%',
background: 'linear-gradient(135deg, #4f46e5, #1e1b4b)',
padding: 80, color: 'white',
}}>
<div style={{ fontSize: 64, fontWeight: 600, lineHeight: 1.1 }}>{title}</div>
<div style={{ marginTop: 'auto', fontSize: 28, opacity: 0.85 }}>{author}</div>
</div>
),
{ width: 1200, height: 630 }
);
}
```
Constraints to know:
- Satori only supports a subset of CSS. **`display: flex` is required on every element with multiple children.** Block-level layout doesn't work as you'd expect.
- Custom fonts: load as ArrayBuffer and pass via `fonts` option. Google Fonts work via fetch.
- No `<img>` from arbitrary sources without explicit width/height
- No background-image gradients on inline elements; use `background` on a flex container
## Path 2: Puppeteer (fully featured)
Headless Chrome screenshot of an HTML page. Use this when you need full CSS, web fonts, complex layouts.
```
// pseudo
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1200, height: 630 });
await page.goto('https://yoursite.com/og-template?title=Foo');
const buffer = await page.screenshot({ type: 'png' });
```
Trade-offs:
- Slower (300-800ms per image)
- Heavier deploy (Chrome binary or `@sparticuz/chromium` for Lambda)
- BUT: full CSS support, web fonts, complex backgrounds
Cache aggressively — generate once per (title, author) tuple, store in R2 / S3 / Vercel Blob.
## Template structure
A solid OG layout, top to bottom or layered:
1. **Background** — solid brand color, gradient, or subtle pattern. Avoid busy photos.
2. **Brand mark** — small logo top-left or top-right (~48px height)
3. **Category / kicker** — small uppercase tag ("Engineering" / "Pricing")
4. **Title** — DOMINANT. `60-80px font-size`, semibold, line-height 1.05-1.15, max 3 lines, ellipsis after
5. **Meta row** — author avatar + name, date, read time (if blog)
6. **Footer mark** — domain ("yoursite.com") bottom-right
Long titles: use `text-overflow: ellipsis` after 3 lines, OR scale font down dynamically based on length (more work, prettier).
## Style guidance
- Use ONE display font, semibold or bold. Inter, Manrope, Geist, Cal Sans all read well at 60px.
- High contrast: white on dark accent, or very dark on light background. Avoid medium-on-medium.
- Avatar: circle, 64-80px, with `border: 2px solid white` for separation
- Don't use brand-cluttered backgrounds. The title should dominate.
- Test on Twitter / Slack at small preview sizes — if title is unreadable, font is too small
## Hooking it up
In your page `<head>`:
```
<meta property="og:image" content="https://yoursite.com/api/og?title=My+Post&author=Jane">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="My Post — by Jane">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://yoursite.com/api/og?title=...">
```
Always include explicit `width`, `height`, and `alt` on the og:image meta — Slack and LinkedIn use them.
## Caching
- For dynamic params: hash them, cache the PNG to blob storage with a long TTL
- Add `Cache-Control: public, max-age=31536000, immutable` if the URL hashes the inputs
- Vercel `@vercel/og` caches at the edge automatically
## Accessibility
OG images are accessibility-adjacent: screen readers don't read them, but social platforms display the `alt` text in some contexts. Always set `og:image:alt`.
If the OG image is the only place a piece of info appears (e.g., a chart's value), reproduce it in body text — the image is decorative as far as a11y is concerned.
## Anti-patterns
- **One static OG image for the whole site**: massive missed opportunity
- **Title cut off at 1 line**: most posts have titles > 8 words. Plan for 2-3 lines.
- **Tiny title with huge brand logo**: invert. Title dominates.
- **Photo background with text on top, low contrast**: unreadable in feeds
- **Forgetting og:image:width/height**: some platforms refuse the image
- **Generating on every request**: cache. Edge or blob.
- **Wrong aspect ratio**: 1200x630 is the standard. Square gets cropped.
## Output
Return the API route + template + meta tag snippet. Top comment lists:
- Implementation chosen (Satori vs Puppeteer)
- Variables supported
- Caching strategy
- Recommended cache key
- A11y note (alt text via og:image:alt)
Example prompts
Once installed, try these prompts in Claude:
- Build an OG image template using @vercel/og: post title, author name + avatar, our brand color, gradient bg.
- Generate a Puppeteer-based OG image API. HTML template + screenshot at 1200x630. Cache to Cloudflare R2.