You’ve just published an article, you share the link on LinkedIn or Bluesky, and then… no preview image appears. Just a sad title and a big empty space.
Inspecting the source code of your page, you stumble upon this horror in the <head> tag:
<meta property="og:image" content="[https://vbesse.com/](https://vbesse.com/)[object%20Object]">
The infamous [object Object] has struck again. Don’t panic, this is a classic error when handling images in Astro. Here’s why it happens and how to bulletproof your BaseHead.astro component once and for all.
Why does Astro do this?
There are generally two culprits behind this behavior:
1. The Local Images Trap (Content Collections)
If you use Astro’s native image imports (for example in your frontmatter: image: "../../assets/my-image.png"), Astro does an amazing job of optimizing it in the background.
But when it returns the image variable to you, it is not a simple string. It is a complex object that looks like this:
{ src: '/_astro/my-image.png', width: 800, height: 600 }.
If you pass this object directly to the content attribute of your <meta> tag, the browser tries to read it as text and spits out [object Object].
2. Forgetting .href with new URL()
For social networks to accept your images, the URL must be absolute (starting with https://). We often use the native new URL(path, Astro.site) function.
However, this function returns a URL object, not text. You must explicitly extract the final string using .href.
The Solution: The Indestructible BaseHead 🛡️
To fix this problem, we are going to modify our <BaseHead /> component so that it can accept anything (an Astro object, a dynamic Satori URL, or nothing at all) and systematically spit out a clean absolute URL.
Here is the JavaScript code (in the frontmatter of your BaseHead.astro):
---
const { title, description, image } = Astro.props;
// 1. Building the Canonical URL
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
// 2. Extracting the slug of the current page
const slug = canonicalURL.pathname
.replace(/^\/|\/$/g, "")
.split("/")
.pop();
// 3. Defining the image path (Priority order)
let imagePath;
if (image) {
// Case A: An image is provided (via Frontmatter)
// We check its type. If it's the Astro object, we extract .src!
imagePath = typeof image === 'object' ? image.src : image;
} else if (slug) {
// Case B: No image, but it's an article (Automatic Satori generation)
// Note: Adapt this path if you are in the /en/ subdirectory
imagePath = `/og/${slug}.png`;
} else {
// Case C: The default Fallback
imagePath = '/default-image.png';
}
// 4. Generating the final absolute string
const socialImageURL = new URL(imagePath, Astro.site).href; // <-- .href saves lives
---
<meta property="og:image" content={socialImageURL} />
<meta name="twitter:image" content={socialImageURL} />
The Final Step: Clear the Cache
So you’ve fixed your code, pushed it to production, but LinkedIn still stubbornly refuses to display your image? That’s perfectly normal; the platform has cached your link with the old error.
- Go to the LinkedIn Post Inspector.
- Enter the URL of your article.
- Click “Inspect”.
This will force the crawlers to re-read your brand-new tags. Your social images are back!