Twitter Card Image Not Showing When Sharing

Sharing your URL on X/Twitter shows no image, even though og:image is set. Why Twitter cards have their own rules.

You paste your article URL into a tweet on X (formerly Twitter), expect the big preview card with a picture, and get either a bare link, a card with title-only, or a stale image you replaced weeks ago. Meanwhile the same URL renders cleanly on LinkedIn and Slack. This is almost never a “Twitter is broken” problem — it is one of four very specific things: missing twitter:card declaration, an og:image / twitter:image URL the crawler cannot fetch, an image that violates X’s size/format rules, or X’s 7-day card cache holding an old crawl. In 2026, X reads Open Graph tags as a fallback, so for most sites you do not need a separate twitter:image — but you do need at least twitter:card.

Identify which case you are in (5 seconds)

  • Card renders with title + description but no imagetwitter:image / og:image URL is unreachable or the file is too small
  • No card at all, just a bare link → missing twitter:card meta tag entirely, or twitter:card value is unrecognized
  • Old image keeps showing after you replaced it → X card cache
  • Works on Facebook / LinkedIn but not Xtwitter:image is a relative path that the X crawler does not resolve, or twitter:card is missing
  • Works for new URLs, broken for one specific URL → that URL’s image returns non-200 (signed URL expired, file deleted)

Common causes, ranked by hit rate

1. Missing twitter:card declaration

Without <meta name="twitter:card">, X has no idea what shape of card to render. It will either fall back to a small summary card or render nothing at all. This is the single most common cause for “no card” on first share.

How to spot it: view-source on the page and search for twitter:card. If absent, this is your problem.

Fix: emit at minimum

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Article title" />
<meta name="twitter:description" content="One-sentence summary" />
<meta name="twitter:image" content="https://yourdomain.com/og/article.png" />

summary_large_image is the wide 1.91:1 card most publishers want. summary is the small square thumbnail card.

2. twitter:image is a relative path

X’s crawler does not resolve relative URLs. content="/og/foo.png" is sent to the crawler as the literal string /og/foo.png and the fetch fails.

<!-- Wrong: relative -->
<meta name="twitter:image" content="/og/article.png" />

<!-- Right: absolute https URL -->
<meta name="twitter:image" content="https://yourdomain.com/og/article.png" />

How to spot it: open view-source, inspect the content attribute. Anything starting with / or ./ is wrong.

3. Image violates X’s size / format rules

X has hard requirements for summary_large_image:

FieldRule
FormatPNG, JPEG, WebP (animated GIF accepted but only first frame used)
Min dimensions300 x 157 px
Max dimensions4096 x 4096 px
Max file size5 MB
Aspect ratio1.91:1 (will be cropped if off-ratio)
ProtocolHTTPS only

A 200x200 favicon, a 6 MB hero shot, or an http:// URL all silently fail. The card may still render but without the image slot.

How to spot it:

curl -A "Twitterbot/1.0" -I "https://yourdomain.com/og/article.png"
# Expect HTTP/2 200, content-type: image/png, content-length under 5000000
identify https://yourdomain.com/og/article.png   # ImageMagick — confirms dimensions

If content-length is over 5,000,000 or dimensions are under 300x157, re-export.

4. X cached an old crawl (7-day cache)

X caches card data per URL for up to 7 days. You fixed the meta tags and republished but tweets still show the old card. There is no public “force re-scrape” button anymore — Twitter killed cards-dev.twitter.com/validator in 2023.

How to spot it: paste the URL into opengraph.dev or cardvalidator.io; they fetch fresh and show what the current tags emit. If the validator shows the new image but X still shows the old one, it is the X cache.

Fix options (in order of effectiveness):

  1. Append a cache-buster query string: tweet https://yourdomain.com/article/foo?v=2 instead of the bare URL.
  2. Wait 7 days — the cache expires.
  3. Change the canonical URL (rare; only if the slug actually changed).

twitter:image points at a Cloudinary signed URL, a CDN that requires a Referer header, or a path behind your auth wall. The Twitterbot user-agent gets a 401 / 403 / 302-to-login.

curl -A "Twitterbot/1.0" -I "https://cdn.yourdomain.com/og/article.png"
# 200 + image/png means OK
# 401 / 403 / 302 means it's gated

Fix: serve OG images from your static public/ directory or whitelist the Twitterbot/1.0 user-agent at the CDN.

Open Graph as fallback (2026 behavior)

As of 2026, X reads Open Graph tags when the twitter: namespace is missing. If you already emit og:image correctly, you can get away with just adding <meta name="twitter:card" content="summary_large_image" /> and skip twitter:image. This is the recommended minimum for content sites in 2026:

<meta property="og:title" content="Article title" />
<meta property="og:description" content="One-sentence summary" />
<meta property="og:image" content="https://yourdomain.com/og/article.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:url" content="https://yourdomain.com/articles/article-slug/" />
<meta property="og:type" content="article" />

<meta name="twitter:card" content="summary_large_image" />

Only override with twitter:image if you want a different image for X than for Facebook/LinkedIn.

Shortest fix path

In hit-rate order:

  1. Add twitter:card if missing → fixes ~45% of “no card” cases
  2. Make twitter:image (or og:image) absolute https → fixes ~25%
  3. Re-export image at 1200x630 PNG/JPEG under 1 MB → fixes ~15%
  4. Cache-bust with ?v=N or wait 7 days → fixes ~10%
  5. Whitelist Twitterbot at the CDN / move image to public/ → the worst 5%

Prevention

  • Standardize on 1200x630 PNG or JPEG, target under 500 KB, served from your apex domain (not a signed CDN).
  • Use a helper that forces every og:image and twitter:image to be an absolute URL with protocol.
  • Add a CI check: curl -A "Twitterbot/1.0" -I against each new article’s OG image, assert 200 + correct content-type + content-length under 5 MB.
  • Skip twitter:image unless you genuinely want a different image for X — let X use og:image as fallback to reduce per-template fields that can drift.
  • Auto-generate OG images from the article title (@vercel/og, satori, puppeteer) so “forgot to add an image” can’t happen.

FAQ

Q: Twitter’s old card validator is dead. How do I test? A: Use opengraph.dev or cardvalidator.io. Both fetch your URL fresh and show how X will render it. For the absolute ground truth, post the URL from a throwaway X account and screenshot the result.

Q: I see the old image even after fixing meta tags. How long until X re-crawls? A: X’s card cache is 7 days. There is no public force-refresh. The fastest workaround is a ?v=2 query string on the tweeted URL — X treats it as a new URL and re-crawls.

Q: Do I need both og:image and twitter:image? A: No. As of 2026 X reads og:image as a fallback. You only need both if you want different images per platform. The one tag X strictly requires is <meta name="twitter:card">.

Q: My card has a picture but it is cropped weirdly. A: summary_large_image enforces 1.91:1. Source images that are 1:1 or 4:3 will be center-cropped. Re-export at exactly 1200x630.

Q: Animated GIF — first frame only? A: Yes. X picks the first frame and freezes it. If the first frame is a black title card, the card looks blank. Use a still PNG/JPEG instead.

Q: My link unfurls fine in DMs but not in tweets. A: DMs use a separate inline preview that ignores cache more aggressively. Tweet rendering uses the card pipeline with the 7-day cache. Behavior diverging between the two confirms it is a cache issue, not a tag issue.

Tags: #SEO #Troubleshooting #Debug #Structured data #Twitter card #OG image