notes 5 min seo
Why the share card shows the wrong image
The page looks right in the browser and the card looks wrong in the chat. That is because they are two different readers: one runs your scripts and fetches fresh, the other reads the raw HTML once and remembers it.
When a link is pasted into a chat, a post or a message, the platform does not render the page. It sends its own crawler, which requests the URL, reads the <head> for a handful of meta tags, fetches the image they point at, and stores the result. The card everybody sees afterwards is built from that stored copy.
<meta property="og:title" content="Why the share card shows the wrong image">
<meta property="og:description" content="A preview is built by a crawler…">
<meta property="og:image" content="https://example.com/cards/share.png">
<meta property="og:url" content="https://example.com/blog/share-card/">
<meta name="twitter:card" content="summary_large_image">
Every wrong card is one of those reads going differently from what the author expected. There are five usual ways.
The crawler did not run your JavaScript #
This is the commonest by a distance. A single-page app sets its title and its Open Graph tags from a router or a head manager, after the bundle has loaded. A browser sees them. The share crawlers generally do not execute scripts, so they see whatever the server sent - typically the tags of the app shell, identical on every route.
Every page shares as the home page is the signature of this bug. The served HTML and the rendered DOM are two different documents, and a card is built from the first one.
The fix is to put the tags in the HTML the server returns: server rendering, static generation, or a small edge function that writes the right <head> per route. Injecting them faster on the client does not help, because the crawler is not waiting for the client at all.
The URL is not one the crawler can use #
- A relative og:image
- The protocol expects an absolute URL.
/cards/share.pngresolves in a browser and is simply dropped by several consumers, which then show no image or pick one from the page body. - An og:url pointing elsewhere
- Some platforms treat
og:urlas the canonical object and read the card from that address. A template that sets it to the site root makes every article share as the home page, even when the article's own tags are correct. - An image the crawler is refused
- A bot-blocking firewall, hotlink protection, an authenticated asset host, or a
robots.txtrule over the image path. Some crawlers honourrobots.txtfor the image as well as the page. The browser is let through; the crawler gets a 403 and the card has no picture. - An image behind redirects
- An
httpaddress that redirects tohttps, then to a CDN. Each redirect is another request the crawler may or may not follow, and an image behind two of them is an image some platforms never reach.
The image is outside what the platform accepts #
Each platform has its own limits, they change, and an image outside them is dropped without an error anywhere you would see it. What holds across the major ones:
- 1200 by 630 is the safe size. It is close to the 1.91:1 ratio the large card layouts use, and large enough not to be upscaled on a high-density screen.
- Too small falls back to a thumbnail layout. Below a platform's minimum the card shrinks to a small square beside the text, or drops the image entirely.
- There is a byte ceiling, in single-digit megabytes. An uncompressed PNG screenshot can be over it.
- SVG is not accepted by the major platforms. Use PNG, JPEG or WebP, and serve it with an image content type rather than a generic one.
Without an og:image at all, most platforms show a title-only card or guess from an image in the page, and the guess is whatever came first in the markup - often a logo or a tracking pixel. Social card builds a 1200 by 630 image from the page's own title and host, with the tags that point a share at it.
The card is cached, and you fixed it after the first share #
A crawler fetches a URL once and keeps the result. Correct the tags, deploy, paste the link again, and the old card comes back - because the platform did not fetch anything. It had an answer already.
- Ask for a fresh read
- Facebook's Sharing Debugger and LinkedIn's Post Inspector both refetch a URL on request. That is the reliable route where it exists.
- Change the URL
- The cache is keyed on the address. A query string on the shared link, or a new filename for the image, is a different object to every platform and gets a fresh fetch.
- Wait
- Where neither is available the stored card expires on the platform's own schedule. Nothing on your side shortens it.
Get the card right before the first share, not after. The first fetch is the one that sticks, and it happens the moment somebody pastes the link - often the author, testing.
Tags that are present but ambiguous #
-
Duplicates. A theme and an SEO plugin each write an
og:image. Platforms disagree about which one wins, so the same page can share differently in two places. -
nameinstead ofproperty. Open Graph is specified with thepropertyattribute. Many consumers accept either; none is obliged to. -
No
twitter:card. X needs it to choose the large image layout. The title, description and image it can take from the Open Graph tags. -
Tags outside the
<head>. A meta tag written into the body by a script or a CMS block is in the document and outside the place a crawler reads.
Seeing what the crawler saw #
The test that settles most of these is to request the page the way a crawler does and read the raw response, not the DOM:
curl -sL -A 'facebookexternalhit/1.1' https://example.com/blog/share-card/ \
| grep -iE '<meta[^>]+(og:|twitter:)'
curl -sIL -A 'facebookexternalhit/1.1' https://example.com/cards/share.png
# want: a 200, an image/* content type, a sane content-length
If the first command prints nothing, or prints the home page's tags, the tags are being added by script. If the second ends in a 403, the crawler is being refused the image even though your browser is not.
From the page itself: Head order reads the tags in the order the parser meets them and names the missing and the duplicated, and Page source shows the DOM after the scripts have run. The SEO tab's rendering view puts the two side by side - what arrived in the HTML against what JavaScript added - which is the comparison every one of these bugs comes down to.
Written by Ján Turský
Building LoupeKit and other browser tools out of Bratislava, under Apptiary.
run it here, no install
tools in this note