Doing this by hand breaks twice: the images drift apart visually, and the tags drift apart from the images. Generating from a single source of truth fixes both problems at once.
Step 1 — Define the parameters, not the pixels
The generator takes five inputs. Everything else — layout, grid, safe area — is fixed by the template so a batch of 200 cards stays visually coherent.
- title — the page headline, up to 120 characters.
- description — optional supporting line, up to 240 characters.
- siteName — the brand label rendered in the header and footer.
- brandColor — hex value; the foreground is picked automatically for contrast.
- template — minimal, gradient, dark or brand.
Step 2 — Keep a parameters file
slug,title,description,template
wordle,Wordle Unlimited — Play free word guessing,Play unlimited rounds. No registration.,minimal
pricing,Pricing — Flat and predictable,One price for checks API and generators.,brand
docs,Developer documentation,Endpoints authentication errors and limits.,dark Step 3 — Render the batch
while IFS=, read -r slug title desc template; do
[ "$slug" = "slug" ] && continue
curl -s -o "og/$slug.png" -G "https://siteassets.dev/api/generator/og" \
--data-urlencode "title=$title" \
--data-urlencode "description=$desc" \
--data-urlencode "siteName=Example" \
--data-urlencode "brandColor=#121212" \
--data-urlencode "template=$template" \
--data-urlencode "format=png"
done < og-params.csv The same call with format=webp or format=svg returns the identical composition in another format, which is what lets you ship a lossless SVG fallback alongside a compressed PNG.
Step 4 — Emit the tags from the same file
const params = new URLSearchParams({
title, siteName: 'Example', brandColor: '#121212',
template, format: 'png',
});
const image = `https://siteassets.dev/api/generator/og?${params}`;
export const meta = [
{ property: 'og:title', content: title },
{ property: 'og:image', content: image },
{ property: 'og:image:width', content: '1200' },
{ property: 'og:image:height', content: '630' },
]; Because the tag and the asset come from one record, the image URL can never point at a card that does not exist. That single property removes the most common cause of broken previews in large sites.
Step 5 — Cache-bust deliberately
Platforms cache aggressively. When a card changes, keep the old URL alive and publish the new one — either under a new filename or with a version query string.
- Name files with a content hash: og/wordle.a1b2c3.png.
- Or append ?v=2 to the generator URL and update the tag.
- Never mutate an existing URL in place and expect platforms to notice.
Step 6 — Verify the batch
- 01 Check every page URL through the API after deploy.
- 02 Let the script exit non-zero when any score drops below your threshold.
- 03 Re-run on every content change, not on a calendar schedule.
One parameters file, one visual language, zero hand-edited cards.
Anonymous generator calls are rate limited per IP. For production batches use an API key — Pro covers 10,000 generator renders a month, which is comfortably more than a 200-page site needs per quarter.