You paste your URL into Slack and the link unfurls to just a title with no image. Or it showed the image once and never again. Or X shows it but LinkedIn doesn’t. Open Graph is a 2010 protocol, but every platform layers its own rules on top: minimum size, scrape timeout, supported formats, and a server-side cache that can pin a bad result for up to a week.
Fastest fix (covers ~70% of cases): make sure your page has one absolute-HTTPS og:image that returns HTTP 200, then force each platform to re-scrape. Run this first:
curl -s "https://yoursite.com/article" | grep -oE 'og:image" content="[^"]+'
If the URL prints and starts with https://, copy it and check it loads:
curl -sI "https://yoursite.com/og.png" | head -1
If that line is HTTP/2 200, your tags are fine and the problem is almost always a stale platform cache — jump to Force-refresh each platform. If either command comes back empty or non-200, work through the causes below.
Which bucket are you in?
| Symptom | Most likely cause | Section |
|---|---|---|
| No image on ANY platform | Missing tag, relative/HTTP URL, or non-200 image | Causes 1, 4 |
| Works in browser preview, not when shared | Stale cache, or scraper timeout on dynamic image | Causes 3, 5 |
| X shows small card, LinkedIn/FB show large | Missing twitter:card or image too small | Causes 2, 6 |
| Worked yesterday, broke after a deploy | Cache holds old result; image URL or path changed | Cause 3 |
| Only Slack fails | Slack image-proxy cache or unsupported format | Cause 7 |
Common causes
Ordered by how often they’re the culprit.
1. og:image URL is relative or HTTP
Open Graph requires an absolute HTTPS URL. Platforms reject <meta property="og:image" content="/images/cover.png"> and most won’t follow a plain http:// URL either.
How to spot it:
curl -s "https://yoursite.com/article" | grep -oE 'og:image" content="[^"]+'
If the printed URL doesn’t start with https://, that’s your bug.
2. Image is too small or wrong aspect ratio
Each platform has its own floor (as of June 2026):
| Platform | Minimum | Recommended | Aspect |
|---|---|---|---|
X (summary_large_image) | 300x157 | 1200x628 | 1.91:1 |
200x200 | 1200x630 | 1.91:1 | |
400x400 | 1200x627 | 1.91:1 | |
| Slack | 200x200 | 1200x630 | any |
A single 1200x630 image (1.91:1) satisfies all four. Below 600px wide, X drops the large card and falls back to summary (no image) even when you declared summary_large_image. LinkedIn renders images under 1200px wide blurry or crops them.
How to spot it: open the og:image URL in a browser and check its pixel dimensions, or run curl -s URL | file - to confirm it’s a real image and not an HTML error page.
3. Platform cached an old result (the most common “worked yesterday” case)
X, Facebook, and LinkedIn cache the scrape result server-side. If the page went live before the image was correct, the platform holds the “no image” entry. As of June 2026 the cache windows are roughly: Facebook ~7 days (a first-seen URL can stick for two weeks), LinkedIn ~7 days, X ~7 days, Slack ~30 minutes. A force re-scrape is the only reliable way to skip the window — see Step 3.
How to spot it: run the page through Facebook’s Sharing Debugger. If it shows the old image (or “no image”), the cache is stale.
4. Image returns non-200
The og:image URL returns 404, 403 (auth required), or 500, so no scraper can fetch it.
How to spot it:
curl -sI "https://yoursite.com/og.png" | head -1
It must be HTTP/2 200. A redirect (301/302) sometimes works but not on every platform; serve the final image directly. Also confirm the Content-Type is image/png, image/jpeg, or image/gif — as of June 2026 Facebook’s docs still list only JPG, PNG, and GIF for og:image, and WebP/AVIF/SVG are unreliable across platforms:
curl -sI "https://yoursite.com/og.png" | grep -i content-type
5. Dynamically generated image is slow or times out
If you generate cards on the fly (Satori-based @vercel/og, now built into Next.js App Router as next/og), the first cold request can take a few seconds. Some scrapers give up after about 2 seconds and treat the image as missing.
How to spot it:
curl -o /dev/null -s -w "%{time_total}\n" "https://yoursite.com/api/og?title=test"
If the warm response is consistently above ~2 seconds, scrapers will time out. The fix is aggressive caching so only the first generation is slow (Step 5).
6. twitter:card meta missing or wrong type
For X to show a large image you need both og:image AND <meta name="twitter:card" content="summary_large_image">. Without it, X uses the small card.
How to spot it:
curl -s "https://yoursite.com/article" | grep twitter:card
You want summary_large_image for a large preview. (X falls back to Open Graph tags for title, description, and image, so you do not need duplicate twitter:image tags — but the twitter:card type is required.)
7. CORS / CSP / Slack image-proxy issue
If the image lives on a different domain with strict CORS or a tight Content-Security-Policy, some platforms fail to fetch it. Slack additionally routes images through its own image proxy (Slack-ImgProxy), which caches separately from the metadata — so even after metadata refreshes, an old image can persist.
How to spot it: serve the image from your own origin (or a permissive CDN), not a third-party host with restrictive CORS. For Slack specifically, see the ?v=2 trick in Step 3.
Shortest path to fix
Step 1: View source and verify the tags
curl -s "https://yoursite.com/article" | grep -E 'og:|twitter:'
You want at minimum:
<meta property="og:title" content="..." />
<meta property="og:description" content="..." />
<meta property="og:image" content="https://yoursite.com/og.png" />
<meta property="og:url" content="https://yoursite.com/article" />
<meta name="twitter:card" content="summary_large_image" />
og:image must be absolute HTTPS. If your framework injects tags client-side (a JS-rendered SPA), scrapers won’t run JS — the tags must be in the raw HTML that curl sees.
Step 2: Verify the image URL returns 200
curl -sI "$(curl -s 'https://yoursite.com/article' | grep -oE 'og:image" content="[^"]+' | sed 's/.*content="//')"
You want 200 and Content-Type: image/png (or jpeg/gif).
Step 3: Force-refresh each platform’s cache
The official X validator at cards-dev.twitter.com/validator was deprecated (it no longer renders a preview), so the workflow per platform as of June 2026 is:
- Facebook / Threads / WhatsApp: developers.facebook.com/tools/debug -> enter URL -> Scrape Again. If the preview doesn’t update, click it 2-3 times; CDN edges can lag up to 24h.
- LinkedIn: linkedin.com/post-inspector -> enter URL -> Inspect. This refreshes the cache for future posts only; already-shared links keep their old preview until you delete and repost.
- X (Twitter): no working official validator. Paste the URL into the X composer (don’t post) and the card renders live from the current tags. Third-party previewers also work for a static check.
- Slack: no public refresh tool and a ~30 min cache. To force a fresh fetch, append a throwaway query param so Slack treats it as a new URL: paste
https://yoursite.com/article?v=2. Deleting the unfurled message also clears its cached entry.
Step 4: Set explicit dimensions and alt text
Declaring size lets platforms render the large card without fetching the image first, which also dodges the timeout in Cause 5:
<meta property="og:image" content="https://yoursite.com/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Plain-language description of the image" />
Step 5: For dynamic OG images, cache aggressively
If you generate images with @vercel/og / next/og, it already sets cache-control: public, immutable, no-transform, max-age=31536000 in production. If you build the response yourself, set the same header so only the first scrape pays the generation cost:
return new ImageResponse(content, {
width: 1200,
height: 630,
headers: {
'cache-control': 'public, immutable, no-transform, max-age=31536000',
},
});
A long max-age means scrapers always hit a cached, fast response. If the card content changes, bust it by changing the URL (a content hash or version param), not by lowering max-age.
Step 6: Confirm it’s actually fixed
Don’t trust a single validator — they sometimes render differently from real posts. After the fix:
- Re-run the Facebook Sharing Debugger and confirm a fresh scrape shows the new image and
og:image:width/height. - Paste the URL into the X composer and confirm the large card renders.
- Run LinkedIn Post Inspector and confirm the preview.
- Share the URL once in a private Slack channel (or with
?v=2) and confirm the image unfurls.
If all four show the image, you’re done.
FAQ
Why does my image show in the browser but not when I share the link? Your browser fetches the page normally; social platforms send a separate scraper bot and then cache the result. The usual reasons are a stale cache from before the image was correct (force a re-scrape, Step 3) or a dynamic image that times out on the first cold request (cache it, Step 5).
I fixed the tag but the old image still shows. How long until it updates? Without a manual refresh, expect roughly 7 days on Facebook, LinkedIn, and X, and about 30 minutes on Slack (as of June 2026). Use the per-platform refresh tools in Step 3 to skip the wait. Note that LinkedIn’s refresh only affects new posts; existing posts keep the old preview.
Can I use a WebP, AVIF, or SVG for og:image?
Treat them as unsupported. As of June 2026 Facebook’s docs list only JPG, PNG, and GIF, and SVG/AVIF render inconsistently across platforms. Export a PNG or JPEG at 1200x630.
Why does X show a small card while LinkedIn shows a big one?
X needs <meta name="twitter:card" content="summary_large_image"> in addition to og:image. Without it X uses the small summary card. It also downgrades to a small/no card if the image is under 600px wide.
My OG tags are correct in dev tools but curl shows nothing. Why?
Your tags are likely injected by client-side JavaScript. Social scrapers don’t execute JS, so the tags must be in the server-rendered HTML. Move them to server-side rendering or a static <head>.
Prevention
- Always use absolute HTTPS URLs in OG tags, and serve the image from your own origin.
- Always include
og:image:widthandog:image:height. - Default to a
1200x630PNG or JPEG. - After any template change, re-scrape on at least two platforms (Facebook Debugger + LinkedIn Inspector).
- Cache dynamic OG images with
max-age=31536000and bust by URL change, not by lowering max-age.
Related
Tags: #Troubleshooting #SEO #Debug #OG image