X removed its public Card Validator, which turned a five-second diagnosis into guesswork. The checks below replace it: each one is observable from the command line or from the post composer, without publishing anything.
One: the card type is missing or wrong
Without twitter:card, X guesses. It usually guesses wrong for wide images. Declare the type explicitly — summary_large_image for a 1.91:1 image, summary for a square thumbnail next to the text.
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="A specific, readable title" />
<meta name="twitter:description" content="One sentence that earns the click." />
<meta name="twitter:image" content="https://example.com/og/1200x628.png" /> Two: the image is below the minimum or above the limit
summary_large_image needs at least 300 × 157; summary needs at least 144 × 144. Both accept files up to 5 MB. An image that is 299 pixels wide fails silently, which is why favicons and thumbnails reused as cards are the most common cause of a text-only post.
Three: the image URL is relative
X does not resolve relative paths at unfurl time. content="/og.png" produces no image. The same applies to og:image. Always include the scheme and host, even when the rest of your head uses relative URLs.
Four: Xbot is blocked
Check robots.txt for a rule that accidentally catches Twitterbot. A CDN or WAF challenge is just as fatal — X does not solve CAPTCHAs. Verify with the user agent X actually sends:
curl -A "Twitterbot/1.0" -I https://example.com/article
# Expect: HTTP/2 200, content-type: text/html
# A 403, 429 or a JavaScript challenge page means the card will never render. Five: the tags are rendered client-side
The crawler does not run your JavaScript bundle. Tags injected by useEffect, onMounted or a client-side head library are invisible to it. View source — not inspect element — and confirm the meta tags are in the raw HTML response.
Six: the URL redirects
X follows a small number of redirects, but cross-domain redirects frequently lose the tags. A link shortener in front of the canonical URL, or an http to https redirect that lands on a page without tags, both break the card. Share the final URL directly when testing.
Seven: you are testing with a URL X will not unfurl
Localhost, private IPs, tunnel URLs that require a header, and pages behind authentication never produce cards. Neither do links to files without an HTML content type. Test with the real public page.
Why the card appears, then disappears
X caches a card once per URL, keyed on the exact string. If you edit the page after the first unfurl, the old card persists. Adding a query string forces a fresh entry for testing, but the cleanest fix is a new image filename plus a re-scrape through the composer.
One more quirk: if a page declares both twitter:image and og:image with different files, X uses twitter:image. If only og:image exists, X falls back to it — but the fallback is less reliable during high load, which is why explicit tags are worth the four lines.