When a shared link shows no image — or yesterday's image — the cause is almost always one of eight things, and the platform's own debugger will tell you which in a minute. The second half of the problem is caching: every platform keeps its own copy of your preview, and each one is refreshed differently. This guide covers both, in the order you should check them.
The eight usual causes
- Relative URL.
content="/og/post.png"works in a browser and fails in every crawler.og:imagemust be an absolutehttps://URL. - The crawler cannot fetch the image. A login, a firewall, bot protection (Cloudflare's bot modes, security plugins), hotlink protection or a
robots.txtrule blocksfacebookexternalhit,LinkedInBotorTwitterbot. Note that Slack does not honour robots.txt for unfurls, so "works in Slack, not on LinkedIn" often points here. - Wrong size or file. Under 200×200 Facebook ignores the image; over 8 MB it rejects it (Meta). LinkedIn wants at least 1200×627, at most 5 MB, and lists only JPG, PNG and GIF (LinkedIn) — a WebP image can fail there while working elsewhere.
- The tags are added by JavaScript. Preview crawlers read the server's HTML; a React or Vue app that sets meta tags after load shows them nothing. Render the tags on the server.
- Two
og:imagetags. A theme and an SEO plugin both print one; the crawler takes the first, which is often the wrong one. - Your own cache serves old HTML. A page cache or CDN still returns the page from before you added the tag.
- The image URL redirects badly or returns the wrong type. Long redirect chains, a
200with an HTML error page, orContent-Type: text/plainon a PNG. - The platform's cache. Everything is fixed, but the platform shows what it fetched last week.
Step 1: see what the crawler sees
Fetch the page the way a crawler does and look at the tags:
curl -sL -A "facebookexternalhit/1.1" https://example.com/blog/post \
| grep -Eio '<meta[^>]+(og:|twitter:)[^>]+>'
If the output has no og:image, the problem is in your HTML (causes 1, 4, 5, 6). If it does, check the image itself:
curl -sIL -A "facebookexternalhit/1.1" https://example.com/og/post.png
You want a final 200, Content-Type: image/png or image/jpeg, and a Content-Length well under 5 MB. A 403 here is cause 2; a text/html content type is cause 7.
Our free OG Checker fetches the page the way Facebook's crawler does, lists what is missing and previews the card on X, LinkedIn, Facebook, Slack and Discord; it can also scan a sitemap for pages without tags.
Step 2: ask the platform
| Platform | Tool | Refreshes the cache? |
|---|---|---|
| Facebook, Messenger | Sharing Debugger | Yes — Scrape Again |
| Post Inspector | Yes, for new posts (LinkedIn) | |
| X | Paste the link into the post composer (no need to post) | No — re-crawled about weekly; change the URL |
| Slack | None needed | Expires after about 30 minutes (Slack) |
| Discord | None | Expires after about 30 minutes (Discord); or add ?v=2 |
| Telegram | @WebpageBot | Yes, for new messages |
| None | No — change the URL |
The Facebook debugger is the most useful diagnostic even if you do not care about Facebook: it shows the exact image it chose, its dimensions, the HTTP response code it got from your server, and a warning when the image could not be downloaded or is too small. Without a manual scrape Facebook refreshes a URL's data about every 30 days; the Graph API can trigger a re-scrape (POST /?id={url}&scrape=true), which helps when you publish many pages at once (Meta). Existing shares keep the data they were created with.
Step 3: bust the cache properly
Platforms cache two things: the page (its tags) and the image (the file behind the URL). Refreshing the page in a debugger does not always refresh the image — Meta's own advice is to "use a new URL for the new image" (Meta).
So when you change an image, change its URL:
<!-- before -->
<meta property="og:image" content="https://example.com/og/post.png">
<!-- after: a new file name or a version parameter -->
<meta property="og:image" content="https://example.com/og/post-v2.png">
Generated images get this for free if the URL depends on the content: a hash of the title in the file name, or a signed URL whose query string contains the title. Change the title and the URL changes with it. Shareglint's signed /i/ URLs and auto-OG /u/ URLs work this way — and an identical image is served from cache without a new render.
For platforms with no refresh tool (X, Discord, WhatsApp), the page URL is the cache key too. X re-crawls cards roughly every seven days and suggests sharing a changed URL to see a fix sooner (archived X docs). New posts pick up a new preview once the cached one expires; to force it now, share a URL the platform has not seen, such as https://example.com/blog/post?v=2. Do not count on previews inside messages and posts that were already sent changing; on most platforms they keep what was fetched at the time.
Framework-specific traps
- Next.js: since v15.2
generateMetadatacan stream its tags after the page body, but Next.js keeps them in<head>for "HTML-limited bots" such asfacebookexternalhit, detected by user agent (Next.js docs). If you serve crawlers through a custom proxy or rewrite the user agent, check that they still get blocking metadata. - Single-page apps (Vite, CRA, Vue CLI): prerender the routes you want shared, or add server-side rendering for meta tags; client-side helmet libraries do not help crawlers.
- WordPress: purge the page cache after changing SEO plugin settings; see automatic OG images in WordPress.
- Static sites: a relative image path in front matter is the classic bug; see OG images for Hugo, Jekyll and Astro.
When the image is generated on request
If og:image points at an image that is rendered when first requested (a signed URL, next/og, your own Puppeteer endpoint), the first crawler visit pays for the render. Crawlers have short timeouts; a cold headless Chrome that needs five seconds can make the first share imageless, and the platform caches that result. Two fixes:
- Warm it: after publishing, request the image once yourself (or paste the URL into the Facebook debugger) before anyone shares it.
- Render ahead of time: generate the image when the post is saved and store the URL, as shown in the Laravel, Rails and Django guides.
A 60-second checklist
-
og:imageis absolute,https://, and appears once in the server HTML. - The image returns
200, an image content type, 1200×630, under 5 MB, JPEG or PNG. - Crawlers are not blocked by a login, firewall, bot protection or hotlink rule.
-
og:image:width,og:image:heightandtwitter:cardare present. - After a change: new image URL, page cache purged, Facebook Scrape Again, LinkedIn Post Inspector.