When a link shares as a bare URL with no title, image or summary, the cause is almost always one of seven things β and they are worth checking in a specific order, because the cheap checks rule out the expensive ones. Work down this list and you will find it. The single most common cause is not a missing tag at all; it is a stale cache on the platform showing you a preview it built before you fixed anything.
Step 1 β Confirm the Tags Exist in the Served HTML
Not in DevTools β in the raw response. The Elements panel shows the DOM after JavaScript has run, which is not what a scraper receives. Fetch the page the way a bot does:
curl -sL -A "facebookexternalhit/1.1" https://example.com/your-page \
| grep -iE '<meta[^>]+(og:|twitter:)'If that prints your tags, the markup is fine and the problem is further down this list. If it prints nothing, you have found the cause β and Step 2 explains the most likely reason.
Step 2 β Check the Tags Are Not Rendered Client-Side
Link scrapers do not execute JavaScript. If your meta tags are set by a single-page app, a tag manager, or a framework helper that only runs after hydration, they exist in the browser and are invisible to Facebook, LinkedIn and Slack. This is the classic React/Vue SPA failure: the tags are right there in the inspector, and the card is still blank.
The fix is to render them server-side. In Next.js:
// Next.js β Pages Router: next/head is rendered into the server HTML
import Head from 'next/head';
export default function Post({ post }) {
return (
<Head>
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
<meta property="og:image" content={`https://example.com/og/${post.slug}.png`} />
<meta property="og:url" content={`https://example.com/blog/${post.slug}`} />
<meta name="twitter:card" content="summary_large_image" />
</Head>
);
}
// Next.js β App Router: export generateMetadata() instead
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
openGraph: {
title: post.title,
description: post.excerpt,
images: [{ url: `https://example.com/og/${post.slug}.png`, width: 1200, height: 630 }],
url: `https://example.com/blog/${params.slug}`,
},
twitter: { card: 'summary_large_image' },
};
}For a pure client-rendered SPA with no SSR option, the usual workaround is prerendering the <head> for known bot user agents at the CDN or edge.
Step 3 β Verify og:image Is an Absolute URL
A relative og:image is silently ignored. Scrapers do not resolve it against the page the way a browser resolves an <img src>, so the card renders with everything except the image β which often reads as "the preview is broken" rather than "the image tag is wrong".
<!-- Ignored β scrapers will not resolve this -->
<meta property="og:image" content="/static/og/launch.png">
<!-- Correct -->
<meta property="og:image" content="https://example.com/static/og/launch.png">The same applies to protocol-relative URLs (//example.com/og.png) and to http:// images on an HTTPS page, which are dropped as mixed content.
Step 4 β Check the Image Is Reachable and Big Enough
The scraper fetches the image separately from the page, as an anonymous request with no cookies. It fails if the image sits behind authentication, hotlink protection, a geo-block, or a rule that only allows same-origin referers. Test it in isolation:
# Should return 200 and an image/* content-type
curl -sI https://example.com/static/og/launch.png | head -n 5
# Also confirm the crawler is allowed to fetch it
curl -sI -A "facebookexternalhit/1.1" https://example.com/static/og/launch.png | head -n 1Size matters too. Facebook and LinkedIn require a minimum of 200 Γ 200 and will upscale anything below 1200 Γ 630, which looks soft. Export at exactly 1200 Γ 630, keep the file under about 1 MB, and declare the dimensions so the platform can lay the card out before the image download finishes:
<meta property="og:image" content="https://example.com/static/og/launch.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Describe what the image shows">Step 5 β Make Sure You Are Not Blocking the Crawler
Bot protection, a WAF rule, Cloudflare's bot fight mode, or an over-broad robots.txt will all serve the scraper a challenge page or a 403 instead of your HTML. From the scraper's point of view your page simply has no Open Graph tags.
# Compare a normal fetch with a crawler fetch β the status codes should match
curl -so /dev/null -w "browser UA: %{http_code}\n" https://example.com/your-page
curl -so /dev/null -w "facebook UA: %{http_code}\n" \
-A "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)" \
https://example.com/your-pageIf the crawler fetch returns 403, 429 or a redirect to a challenge, allowlist the major link scrapers. The user agents worth permitting are facebookexternalhit, Twitterbot, LinkedInBot, Slackbot-LinkExpanding, WhatsApp, TelegramBot and Discordbot.
Disallow covering the page β or the directory your og:image lives in β stops well-behaved scrapers outright. LinkedIn and Slack both respect robots.txt for the image fetch, not just the page fetch.Step 6 β Follow the Redirects
Scrapers follow redirects, but the tags must exist on the final URL, and some platforms cap the number of hops. Short links, marketing redirects, HTTPβHTTPS, and the apexβwww jump can stack up quickly. Trace the chain:
curl -sIL https://example.com/your-page \
| grep -iE '^(HTTP/|location:)'Keep the chain to one or two hops, and make sure og:url on the destination points at the canonical address. A mismatch between the shared URL and og:url is also what causes engagement counts to split across two variants of the same page.
Step 7 β Force a Re-Scrape to Clear the Cached Preview
This is the step people skip, and it is the most common reason a correct fix appears not to work. Every platform caches the scrape result β often for a day or more β keyed by the exact URL. Until you force a refresh, you will keep seeing the preview it built before your fix.
- Facebook β Sharing Debugger at
developers.facebook.com/tools/debug/. Paste the URL and click Scrape Again. - LinkedIn β Post Inspector at
www.linkedin.com/post-inspector/. Inspecting a URL re-scrapes it automatically. - X β the Card Validator. Note that X now caches for around seven days and is the slowest to refresh.
- Slack β no public debugger. Appending a harmless
?x=1query string produces a new cache key and forces a fresh unfurl. - WhatsApp / iMessage β no debugger and aggressive caching; the query-string trick is the practical workaround.
curl or the checker first, then refresh the platform.Verify the Fix
Once the tags are corrected and the cache is cleared, confirm end to end. Re-run the URL through the Open Graph Checker β it should report every required tag present, the image dimensions declared, and a card type of summary_large_image. Then paste the link into a private Slack channel or a draft post: the card should render immediately with the image, headline and summary you expect.
Frequently Asked Questions
Why does the preview work on Slack but not Facebook?
Slack re-scrapes more eagerly and is far more forgiving about image dimensions, so a Slack preview proves your tags exist but not that they satisfy Facebook. Facebook enforces minimum image sizes and caches for much longer. Check the image is at least 200 Γ 200 (ideally 1200 Γ 630) and run the Sharing Debugger.
How long does it take for a fixed preview to appear?
Immediately, if you force a re-scrape in the platform's debugger. If you wait for the cache to expire on its own it is typically 24 hours to 7 days depending on the platform, with X the slowest. There is no reason to wait β use the debugger.
My preview shows an old title and image. Is that the same problem?
It is the caching half of it. A stale wrong preview and a stale missing preview have the same fix: force a re-scrape. If the old content persists after a successful re-scrape, check that you do not have duplicate og:title tags in the page β scrapers generally take the first occurrence, which may be a hard-coded default in your base template.
Do I need different tags for WhatsApp and iMessage?
No. Both read standard Open Graph tags. They cache aggressively and offer no debugger, so the query-string trick (?v=2) is the only reliable way to force a fresh unfurl during testing.
Does the og:image have to be square for some platforms?
No. A single 1200 Γ 630 image at 1.91:1 works everywhere. Platforms that display a square thumbnail (X's summary card, some messaging apps) centre-crop your image rather than rejecting it β so keep the important content away from the edges.