Open Graph tags are not read by a browser. They're read by a scraper — Slackbot, facebookexternalhit, Twitterbot, Discordbot — that fetches your raw HTML over one request, never runs your JavaScript, and gives up after a few seconds. If your og:image gets injected client-side by React after mount, the scraper sees nothing and the preview comes back blank. That single fact explains most "why does my link look broken in Slack" tickets.
<meta property="og:title" content="Article title">
<meta property="og:description" content="Useful summary">
<meta property="og:image" content="https://example.com/og.jpg">
<meta property="og:url" content="https://example.com/article">Everything a preview needs has to be in the HTML the server returns, with the image at an absolute URL. content="/og.jpg" looks fine in your <head> and resolves to nothing for a scraper that has no page origin to resolve against — it wants https://example.com/og.jpg spelled out.
Once a platform has scraped a URL, it caches the result — Facebook and LinkedIn for days. Fixing the tag is not enough; you have to force a re-scrape. Facebook's Sharing Debugger, LinkedIn's Post Inspector, and posting to a fresh Slack channel all bust the cache. Otherwise your correct new tag sits behind yesterday's broken preview for a week.
Debugging a blank Slack preview
The failure is almost always the image, and almost always one of three things. Here's a real triage on a page whose card renders with title and text but no picture:
| Step | What you check | What broke it |
|---|---|---|
| 1 | curl -A "Slackbot" https://you.com/post/ | grep og:image | Tag present in raw HTML? If not, it was client-rendered — move it to SSR |
| 2 | Open the og:image URL directly | 302 to a login wall, or a relative path resolving to 404 |
| 3 | Image is 400×210, 6 KB | Below the 200×200 floor / odd ratio — platforms silently drop it |
The fix: serve a 1200×630 PNG or JPG (the 1.91:1 ratio Facebook and LinkedIn crop to), under a few hundred KB, at an absolute HTTPS URL, in the server-rendered HTML. Add og:image:width and og:image:height so the preview reserves space before the file downloads, and twitter:card set to summary_large_image so X renders the big card instead of the postage-stamp one.
The reason to source all of this from the same page data that renders the visible content is drift: a hand-maintained og:title becomes a lie the day someone edits the <h1> and forgets the meta tag. Generate both from one field. If you want to see what four scrapers make of a live URL, the Open Graph checker fetches it the way they do.
Cover photo by Stanislav Kondratiev on Pexels.
