Updated 2026-10-21
og:image not showing in Slack, LinkedIn or X
You share a link and the card has a title and a description but no picture, or a tiny thumbnail instead of a large image. The image in a link preview comes from the page's og:image tag (and twitter:image for X). When it's missing, unreachable or in a format the previewer rejects, the card drops it without telling you why. This guide covers the image specifically; for the full set of tags, see Open Graph tags for every page.
The usual causes
- A relative URL.
<meta property="og:image" content="/og.png">doesn't work reliably. The Open Graph protocol expects a full URL, starting withhttps://and your domain. - The wrong domain. The URL points to
localhost, a preview deployment or a staging host, because the site URL wasn't set for the production build. - An SVG. Facebook, LinkedIn and X don't render SVG preview images. Use JPG, PNG, WebP or GIF.
- Too small or too heavy. LinkedIn shows images under 401 pixels wide as a thumbnail and recommends 1200×627 at most 5 MB (LinkedIn: make your website shareable). An image around 1200×630 works on every network.
- The image is blocked. The page loads in your browser, but the previewer's request for the image gets a 403, a login page or a bot challenge: password-protected previews, firewall bot rules, or hotlink protection on the image host.
- Added by JavaScript. Previewers read the HTML the server sends and don't run scripts. A tag set at runtime in a single-page app never reaches them.
- A cached card. LinkedIn and Facebook keep a preview for days. You fixed the tag, but the card still shows the old one until you ask them to fetch the page again.
nameinstead ofproperty. The Open Graph spec usesproperty="og:image"; X's tags usename="twitter:image". Some previewers accept either, but not all, so follow the spec.
Check what the previewer gets
First the tag, from the raw HTML:
curl -s https://example.com/blog/post | grep -oE '<meta[^>]+(og|twitter):image[^>]*>'
Then the image itself: it should return 200 and an image content type, with no redirect to a login page.
curl -sI https://example.com/og/post.png | grep -iE '^(HTTP|content-type|content-length)'
The free SEO checker does both from a server, the way previewers do, and shows the LinkedIn, Facebook, Slack and X cards it would produce. After a fix, refresh the cached card in LinkedIn Post Inspector and the Facebook Sharing Debugger (Scrape Again).
Astro
Set site in astro.config.mjs to your production URL, then build the absolute URL in your layout with Astro.site:
---
// src/layouts/Base.astro
const { title, description, image = '/og/default.png' } = Astro.props;
const imageURL = new URL(image, Astro.site).href;
---
<head>
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={imageURL} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={imageURL} />
</head>
Without site, Astro.site is undefined and new URL throws on a relative path, which is better than shipping a broken tag. Put shared images in public/ so their URL doesn't change between builds; an image imported through astro:assets gets a hashed file name, so pass its .src to new URL rather than writing the path by hand.
Starlight sets og:title, og:description and twitter:card but no image by default. Add one for every page with the head option:
starlight({
title: 'My Docs',
head: [
{ tag: 'meta', attrs: { property: 'og:image', content: 'https://docs.example.com/og.png' } },
],
})
Next.js (App Router)
Set metadataBase once in the root layout. Relative image paths in metadata are then resolved against it; without it, a relative path fails the build (Next.js: metadataBase).
// app/layout.tsx
export const metadata = {
metadataBase: new URL('https://example.com'),
openGraph: { images: '/og/default.png' },
}
The pitfall: metadata from layouts and pages is merged shallowly. A page that sets any openGraph field replaces the whole openGraph object from the layout, image included:
// app/blog/[slug]/page.tsx: this page loses the layout's og:image
export const metadata = {
openGraph: { title: 'My post' },
}
Repeat the image in the page's openGraph, spread a shared object into it, or use the file convention: an opengraph-image.png (or .jpg) in a route folder applies to that route and the routes below it.
Hugo
Use Hugo's embedded templates in your <head>:
{{ partial "opengraph.html" . }}
{{ partial "twitter_cards.html" . }}
They take the image from the page's images front matter, then from a page resource whose name contains feature, cover or thumbnail, then from params.images in your site configuration (Hugo: embedded templates). Give the site a default:
# hugo.toml
baseURL = 'https://example.com/'
[params]
images = ['og/default.png']
---
title: My post
images: ['og/my-post.png']
---
Absolute URLs are built from baseURL, so check it's the production address in the environment that deploys. Older themes call the templates as {{ template "_internal/opengraph.html" . }}; recent Hugo versions use the partial form shown above.
If the tag is right and the card still has no image
- Open the image URL in a private window. If you see a login page, so does the previewer.
- Check your host's bot protection and any password protection on the domain you shared.
- Look for two
og:imagetags with different values, for example one from your theme and one from a plugin. Previewers usually take the first. - Ask LinkedIn and Facebook to fetch the page again. Slack and X also cache cards, so a link shared before the fix may keep its old preview.
Automating it with Headlark
Headlark's build step writes the link preview tags into each page's HTML after your build. For the image it's deliberately conservative:
- It keeps your page's existing
og:imageif there is one, otherwise it picks the page's most representative image. - Pages with no usable image, such as docs and text-only guides, get the default share image you set for the site in the dashboard, so no page goes out without a picture.
- It makes the URL absolute from your
--site-urland never uses an SVG or adata:URL, so a page whose only image is an SVG logo gets a small card. - It doesn't create images. If you want a branded card per page, generate them yourself and set
og:image; Headlark then fills in the title, description and the rest around it.
Try it
The free plan covers one site and 25 pages. Add your site, copy its secret key, and your next build ships with SEO.