Paste your own blog post into Slack or X and look at the preview card. If it's a gray box, or your site's logo stretched into a banner, or nothing at all — that's not a small cosmetic bug. It's the single biggest lever you're not pulling on click-through from every place your link gets shared.
Here's what's actually going on, the three ways to fix it, and a copy-pasteable path to a real one in about five minutes.
What an OG image actually is
When you share a link, the platform (Slack, X, iMessage, LinkedIn, Discord) doesn't render your page — it fetches the URL, reads a handful of <meta> tags out of the <head>, and builds a card from those. The one that controls the picture is:
<meta property="og:image" content="https://yoursite.com/some-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
If that tag is missing, most platforms fall back to grabbing the first <img> on the page (often wrong) or nothing at all (the gray box). The fix is always the same shape: get a real 1200×630 image at a stable URL, and point og:image at it.
Three ways to generate that image
1. Design it by hand
Open Figma, make a 1200×630 canvas, drop in your title, export a PNG, upload it, hardcode the URL. Works fine for a homepage or a handful of static pages. Falls apart the moment you have a blog, docs site, or anything with more than a few dozen URLs — you either stop updating the images or you stop shipping pages.
2. Roll your own renderer
Libraries like Satori (which powers Vercel's @vercel/og) let you write a layout in JSX-like syntax and render it to an image server-side, per-request, with the real title baked in. This is the "free" option, and it's genuinely good — but "free" undersells the actual cost. You still have to: write the layout, source and embed your own fonts (system fonts usually aren't available in these edge runtimes), deploy a function to run it, and build a cache in front of it, because otherwise every crawler hit and every unfurl re-renders the same image from scratch.
3. Use a hosted or self-hosted API
Skip building the renderer and call an endpoint instead: send a title (and a few optional params), get back a PNG, drop the URL into your og:image tag. This is the fastest path if the problem you have is "I need this working today," not "I want to own a rendering pipeline."
The 5-minute path
We built SnapOG for exactly this — it's open source and self-hosted, so there's no third party sitting in your request path and no account to wait on. You deploy it to your own free Cloudflare account.
- Deploy it. Click the button on the GitHub repo — it's a standard "Deploy to Cloudflare Workers" flow, no config file to hand-edit first.
- Register a key on your own instance:
curl -X POST https://your-worker.your-subdomain.workers.dev/register - Generate an image:
curl "https://your-worker.your-subdomain.workers.dev/og?title=My+Blog+Post&domain=myblog.com&key=sk_YOUR_KEY" \ --output og.png && open og.png - Wire it into your page's
<head>:<meta property="og:image" content="https://your-worker.your-subdomain.workers.dev/og?title=YOUR_TITLE&key=YOUR_KEY" /> <meta property="og:image:width" content="1200" /> <meta property="og:image:height" content="630" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:image" content="https://your-worker.your-subdomain.workers.dev/og?title=YOUR_TITLE&key=YOUR_KEY" />
That's the whole integration. The endpoint takes a title (required) and a handful of optional params — description, author, tag, domain, plus template (default / blog / article) and theme (dark / light). First request renders and caches to R2; every request after that for the same URL is served from cache in well under 100ms — you can see it working via the X-Cache: HIT|MISS response header.
What this trades away
Three fixed templates, not a design tool. No custom fonts, logos, or arbitrary layouts. That's deliberate — if you need pixel-exact brand templates, a full design platform is the right tool, not this. What you get instead is zero configuration and a title-in, PNG-out endpoint that a single curl command can prove works.
If you're currently hand-maintaining Satori/@vercel/og yourself and just want the caching layer solved, this is worth a look even if you never touch the hosted version — it's MIT-licensed and the whole thing is one Cloudflare Worker.
Try the live template preview or grab the code.