Saturday, August 15, 2026

How to Generate OG Images at Scale: A Developer's Guide

How to Generate OG Images at Scale: A Developer's Guide

The link preview is the first thing people see before they click. On X, LinkedIn, Slack, and iMessage, your URL renders as a 1200×630 card — and if that card is a generic gray box with your domain name, you've already lost the click to the person who posted a screenshot instead.

Dynamic Open Graph images fix this. Instead of one static PNG per page, you render a unique card per URL — title, author, reading time, a chart, whatever your page actually contains. The result is a measurable lift in click-through rate and share velocity. But how you generate those images matters more than whether you do it at all. Get the architecture wrong and you'll ship broken cards, burn server resources, or lock yourself into a tool that can't scale.

Here's the landscape, the trade-offs, and a pipeline that holds up under real traffic.

The Landscape: Client-Side vs. Server-Side Rendering

Client-side generation is the trap. Tools like myogimage.com let you design a template in the browser and export a static PNG. That's fine for a one-off — but it collapses the moment you need dynamic content. You can't render a card for a blog post that doesn't exist yet, and you can't automate a pipeline around a GUI. Worse, some of these "free" tools advertise an API that doesn't actually exist. If your OG strategy depends on a service that can't be called programmatically, you don't have a strategy — you have a screenshot.

The other client-side trap is generating images in the browser at request time. Social scrapers (Twitterbot, Slackbot, LinkedInBot) don't execute JavaScript. If your OG image is rendered client-side, the scraper sees nothing. This is the single most common reason "my OG image works in the preview but not when I share it."

Server-side rendering is the only approach that works reliably. The scraper makes a GET request, your server returns a complete PNG. No JavaScript required. Within server-side, you have two real options:

  1. DIY with satori + a deployment platform (the Vercel approach). satori converts JSX to SVG, then you rasterize to PNG with resvg or sharp. It's fast, it's free, and it's fully under your control. The cost is yours too: you maintain the rendering service, handle font loading, manage concurrency, and debug edge cases yourself. It's a solid choice if you have the time and the traffic justifies the infrastructure.

  2. A dedicated OG image API (like FastOG). You send a GET request with your template and parameters, the service renders the image server-side, caches it at the edge, and returns a URL. No infrastructure to maintain, no fonts to bundle, no scraper compatibility to test. You pay per image, and caching means repeat renders cost nothing.

The right choice depends on your constraints. If you're an indie hacker shipping a blog this weekend, the DIY stack is a fun afternoon project. If you're building a SaaS where every page needs a unique card and your time is better spent on the product, a managed API wins.

Anatomy of a High-Performance OG Image Pipeline

A production-grade pipeline has five stages. Here's what each one needs to do.

1. URL Design

Your OG image endpoint should be a simple GET request with query parameters — no auth headers, no POST bodies. Social scrapers only send GET requests. FastOG's pattern looks like this:

https://api.fastog.com/api/v1/og?template=blog&title=Hello%20World&author=Jane

Keep the parameter surface small. Every parameter is a template variable you need to document, validate, and test. Start with 3–5 per template.

2. Template Registry

A template is a design with named slots: title, author, date, reading_time, accent_color. Your registry maps a template ID to its layout and default styles. FastOG ships 41 templates covering OG cards (1200×630) and X headers (1500×500) — but you don't need 41. You need a handful that match your brand and a registry that makes adding new ones trivial.

3. Rendering

The renderer takes the template, injects the parameters, and produces a PNG. This is where satori-style approaches and managed APIs diverge. The key performance metric is time-to-first-byte: social scrapers are impatient. If your render takes longer than ~2 seconds, platforms will fall back to a generic card. FastOG renders via Svelte → headless Chrome, which handles complex layouts and custom fonts gracefully, and serves the result from edge cache.

4. Caching

This is the difference between "paying per image" and "paying per unique image." If your pipeline caches at the CDN edge, the first render of a URL is the only render that costs anything. Every subsequent scrape — and there will be many, every time someone shares the link — hits the cache. FastOG's model is 1 credit = 1 image, with 0 cost on cache hits. If you're DIY, make sure your cache layer is in front of your renderer, not behind it.

5. Security

If your OG endpoint accepts query parameters, it's a public URL. Anyone can hit it. That's fine for public content, but it means you need HMAC signing for anything you don't want rendered arbitrarily. The pattern:

// Node.js example: sign an OG image URL
const crypto = require('crypto');

function signOgUrl(baseUrl, params, secret) {
  const query = new URLSearchParams(params).toString();
  const signature = crypto
    .createHmac('sha256', secret)
    .update(query)
    .digest('hex');
  return `${baseUrl}?${query}&sig=${signature}`;
}

// Usage
const url = signOgUrl(
  'https://api.fastog.com/api/v1/og',
  { template: 'blog', title: 'Hello World' },
  process.env.OG_SECRET
);

The server recomputes the HMAC and rejects requests with an invalid or missing signature. This prevents abuse of your render quota and stops people from generating arbitrary images on your dime.

Putting It Together

Here's a minimal implementation for a blog, using a managed API:

// lib/og.js
const OG_BASE = 'https://api.fastog.com/api/v1/og';
const OG_SECRET = process.env.OG_SECRET;

export function getOgImageUrl({ title, excerpt, slug }) {
  const params = {
    template: 'blog',
    title,
    excerpt,
    slug,
  };
  const query = new URLSearchParams(params).toString();
  const sig = crypto.createHmac('sha256', OG_SECRET).update(query).digest('hex');
  return `${OG_BASE}?${query}&sig=${sig}`;
}

Then in your page template, add the meta tags:

<meta property="og:title" content={title} />
<meta property="og:image" content={getOgImageUrl({ title, excerpt, slug })} />

That's it. The scraper fetches the image URL, your API renders and caches it, and every share of that page shows a custom card.

The Bottom Line

Client-side OG tools are fine for a static site with five pages. The moment you need dynamic cards — per-post, per-user, per-product — you need a server-side pipeline. Whether you build it with satori or buy it from a managed API depends on your time budget and traffic. But the architecture is non-negotiable: GET-based URL, template registry, fast renderer, edge cache, and HMAC signing.

Start with one template and one page type. Measure your click-through rate before and after. When you see the lift, expand to the rest of your site. The first card is the hardest — everything after that is just filling in the template.


Source: DEV Community

Previous Post
Next Post

post written by: