You paste your link into Slack, or Twitter, or iMessage, and instead of a nice card with a title and an image you get… a blank grey box. Maybe the raw URL. Maybe your site's name in tiny letters and nothing else.
This is one of those bugs that never shows up in your test suite, never breaks a page, and quietly costs you clicks on every single share for the entire life of the site. Here's exactly what's happening and how to fix it in about ten minutes.
What actually happens when you paste a link
Every platform that renders a link preview does roughly the same thing:
- It fetches your URL with its own bot user-agent (
Twitterbot,facebookexternalhit,Slackbot-LinkExpanding,Discordbot). - It reads only the HTML that comes back in that first response.
- It looks for Open Graph
<meta>tags in the<head>. - If it finds an image URL, it fetches that separately and caches the whole thing.
Two consequences fall straight out of that list, and they explain the majority of blank previews:
-
It does not run your JavaScript. If your meta tags are injected client-side by React/Vue/Svelte after hydration, the crawler sees an empty
<head>and gives you nothing. This is the single most common cause. -
It only reads the
<head>. Tags placed in the<body>, or after a large inline script that breaks parsing, get ignored.
The four tags that do 95% of the work
<meta property="og:title" content="Your page title, under 60 characters">
<meta property="og:description" content="One sentence, 110-140 characters, written for a human.">
<meta property="og:image" content="https://example.com/og.png">
<meta property="og:url" content="https://example.com/the-canonical-url">
Note property=, not name=. Open Graph uses RDFa, so <meta name="og:title"> is silently ignored by most parsers. This trips up people constantly because it looks right.
Add two more and you have full coverage:
<meta name="twitter:card" content="summary_large_image">
<meta property="og:type" content="website">
twitter:card is the one people forget. Without it, X/Twitter defaults to the small square thumbnail rather than the big banner card — same tags, a fraction of the visual footprint. And yes, twitter:* uses name= while og:* uses property=. It's inconsistent. Just copy it correctly.
Getting the image right
The image is where most "I added the tags and it still looks bad" cases end up.
-
Absolute URL, always.
/og.pngwill not work. Crawlers do not resolve relative paths reliably. It must start withhttps://. - 1200x630 is the size to use. That's the 1.91:1 ratio every large-card renderer expects. Anything squarer gets cropped, usually straight through your text.
- Keep it under ~1MB. Some crawlers have a hard timeout and simply give up on a 4MB hero PNG.
- No redirects on the image URL. Several crawlers won't follow them.
-
It must be publicly reachable. If your image sits behind auth, a signed CDN URL that expires, or a
staging.host that requires basic auth, the crawler gets a 401 and shows nothing. - Put readable text in the image. The card is a billboard, not a decoration. Title plus logo beats an abstract gradient every time.
The JavaScript problem, concretely
If you're on a client-rendered SPA, react-helmet and friends update the DOM in the browser. The crawler never gets there. Your options, roughly in order of how much work they are:
- SSR or SSG. Next.js, Nuxt, SvelteKit, Astro, Remix — all of them put the tags in the initial HTML. This is the real fix.
- Prerendering. A build step or middleware that serves static HTML snapshots to bot user-agents.
- Edge middleware. Cloudflare Workers / Netlify Edge injecting the tags into the response before it ships.
Quickest way to know which camp you're in — fetch your own page the way a crawler does:
curl -sL https://yoursite.com | grep -i 'og:'
If that prints nothing but the tags are visible in devtools, your tags are client-side only. That's your answer. Devtools shows you the hydrated DOM; curl shows you what the crawler actually receives. Always trust curl here.
Caching: why your fix "didn't work"
You fixed the tags, you re-pasted the link, and it's still the old broken preview. That's caching, not failure. Every platform caches aggressively — often for days.
- Facebook / Instagram: Sharing Debugger → "Scrape Again".
- LinkedIn: Post Inspector.
-
X/Twitter: no longer offers a public flush; changing the URL (add
?v=2) forces a fresh fetch. -
Slack / Discord: cache per-URL for hours; the
?v=2trick works there too.
Test with a brand-new URL you've never shared, and you'll see the true current state.
A five-minute audit for a site you already shipped
Run through this list once per site:
-
curl -sL yoursite.com | grep -i 'og:\|twitter:'— do the tags exist in the raw HTML? - Are they inside
<head>? - Is
og:imagean absolutehttps://URL? - Does that image URL return 200 on its own, with no redirect? (
curl -sI <image-url>) - Is it roughly 1200x630?
- Is
twitter:cardset tosummary_large_image? - Is
og:urlthe canonical URL, not the staging host? (Genuinely common — a hardcodedstaging.inog:urlsends every click on every share to a dead host.) - Do your inner pages have their own tags, or does every URL share one generic homepage card?
That last one is worth dwelling on. Plenty of sites set OG tags once on the homepage and call it done. Then every blog post, every product page, every deep link shares the identical card, and nobody clicks because the preview tells them nothing about the page they were actually sent.
Why it's worth the ten minutes
A link preview is the only advertisement you get that costs nothing and appears every time someone recommends you. It shows up in the DM where a customer forwards you to a colleague, in the Slack channel where someone drops your docs, in the tweet where somebody vouches for you. A grey box in those moments reads as broken or abandoned — and a link that looks broken doesn't get clicked, no matter how good the thing behind it is.
Ten minutes, six tags, one correctly-sized image. Nothing else on your pre-launch list has that ratio.
I got tired of checking this by hand on every site I ship, so I built a free tool that does it for me: LaunchAudit fetches your page like a crawler does and grades OG/Twitter cards alongside security headers, mobile viewport, robots/sitemap, structured data and broken links — one 0-100 launch-readiness score, no signup. Paste a URL and you'll know in ten seconds whether your previews are actually there.
Top comments (0)