Astro is a strong fit for Open Graph because most pages ship as static HTML. Crawlers see the same tags you put in the layout—as long as you do not hide them behind client-only scripts. This guide shows a practical layout pattern, absolute URLs, per-page images, and verification.
SocialImageKit itself is built with Astro; the same “tags in the first response” rule applies to your project.
Why Astro helps social previews
Facebook, LinkedIn, WhatsApp, and X fetch HTML. They do not execute your islands the way a user browser does. If og:image is in the built .html file, you are ahead of most SPAs. Compare with OG images for React SPAs when a team is stuck on client-only meta.
Configure site and absolute URLs
In astro.config.mjs:
import { defineConfig } from "astro/config";
export default defineConfig({
site: "https://example.com",
trailingSlash: "always",
});
Use Astro.site (or your own constants) when building canonical and og:url values so every tag is absolute. Relative /og/cover.png values are a common production failure.
Layout / SeoHead pattern
Centralize tags in one component and pass props from each page:
---
interface Props {
title: string;
description: string;
canonical: string;
ogImage: string;
}
const { title, description, canonical, ogImage } = Astro.props;
const origin = Astro.site?.toString().replace(/\/$/, "") ?? "https://example.com";
const image = ogImage.startsWith("http") ? ogImage : `${origin}${ogImage}`;
const pageUrl = canonical.startsWith("http") ? canonical : `${origin}${canonical}`;
---
<meta charset="utf-8" />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={pageUrl} />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={pageUrl} />
<meta property="og:image" content={image} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={image} />
On each page:
---
import SeoHead from "../components/SeoHead.astro";
---
<html>
<head>
<SeoHead
title="Launch notes"
description="What shipped this week"
canonical="/blog/launch/"
ogImage="/og/launch.png"
/>
</head>
<body><!-- … --></body>
</html>
Content collections and MDX
For guides or blog posts, store ogImage (and titles) in frontmatter, then pass them into the layout when you render the entry. Keep images under public/og/ so paths stay stable after build.
Create artwork with the OG Image Generator, check weight with the Image Resizer / Social Image Checker, and validate live URLs with the Link Preview Checker.
Hybrid / SSR modes
If you enable server rendering for some routes, still ensure the initial HTML includes OG tags. Do not rely on client islands to inject og:image after load. Endpoints that generate images dynamically must return quickly and allow anonymous GET.
Verify and refresh caches
npm run buildand open the file underdist/or your host.- View Source on the public URL.
- Run the Link Preview Checker.
- Re-scrape Facebook/LinkedIn when testing updates (Sharing Debugger landing, Post Inspector landing).
Cheat sheet: OG tags. Universal failures: og:image not showing.
Common mistakes
- Forgetting
sitein the Astro config so URLs stay relative. - Putting images only in
src/assets/without a public URL strategy. - Different
og:urland canonical hosts (wwwvs apex). - Assuming local
astro devproves crawler visibility—always check production HTML.
Checklist
siteset to your https origin- Shared SeoHead (or equivalent) on every indexable page
- Absolute
og:image~1200×630 twitter:cardset when you care about X- Checker + View Source before campaigns
Next.js teams: How to set an OG image in Next.js. WordPress: OG image in WordPress.
Content collections and editorial workflow
Astro content collections make it natural to store title, description, and ogImage beside the article body. Editors should update those fields when the story changes—not only the Markdown heading. If your pipeline generates social images offline, name files after the slug and document the mapping so new posts do not silently reuse a default /og/guides.png.
Islands architecture can confuse newcomers: interactive search widgets or carousels do not need to own meta tags. Keep SeoHead (or equivalent) in the zero-JS head path. If a third-party script injects meta at runtime, remove it; crawlers will not see it consistently.
For hybrid SSR routes, log the HTML of a sample response in staging. Confirm og:image is present before authentication gates, localization rewrites, or A/B tools rewrite the document. Trailing-slash policy and canonical hosts should match what you put in og:url to avoid Facebook scraping a different document than the one you edited.
Field notes from real share failures
Most broken previews are boring: a relative URL, a duplicate plugin tag, a cache that nobody re-scraped, or a file that is fine in Chrome but too heavy for a messenger. Resist the urge to rewrite the entire brand system until those basics are proven in View Source and a private window.
Write down the exact URL string you tested, including trailing slash and query parameters. Support threads go sideways when one person scrapes example.com/page and another shares example.com/page/. Treat the share string as part of the specification.
When you finally change artwork, change the path. Overwriting og.png in place is the most common reason a “fixed” card never appears. Pair the new file with a short note in your changelog so the next person knows which version is live.
Verification habits worth repeating
After every deploy that touches templates or SEO plugins, spot-check three URLs: the homepage, one article or product, and one utility page. Look for a single absolute og:image and open that file logged out. This ten-minute habit catches more regressions than redesigning share cards.
Document the owner of social tags for your stack—theme, plugin, or layout component—in the repository README. When something breaks, people should know which lever to pull first. Ambiguous ownership is how duplicate tags accumulate.
Finally, separate “works in our browser” from “works for crawlers.” If you only validate inside an authenticated preview session, you will ship failures to Facebook, LinkedIn, Slack, Discord, WhatsApp, and X without noticing until a campaign is live.
If you only remember one rule, remember this: crawlers are blunt. They fetch a URL, read tags, fetch an image, and cache aggressively. Your CMS preview, authenticated browser session, and hydrated React tree are not part of that path. Design the HTML and the image file for the crawler first, and human polish second. That ordering keeps share cards reliable while you iterate on the visible page design.
Frequently asked questions
Short answers from this guide.
How do I add og:image in Astro?
Render meta tags in a shared layout or SeoHead component using absolute https URLs. Set site in astro.config.mjs so Astro can build canonical and OG URLs correctly.
Do View Transitions break Open Graph?
Social crawlers fetch a fresh document; they do not use your in-browser transitions. Keep tags in the static HTML for each URL.
Where should OG image files live?
Usually under public/ so they are copied to the site root (for example /og/post.png). Reference them with a full https URL including your domain.
Can I generate OG images in Astro?
Yes—at build time with scripts, or via an endpoint if you deploy functions. For many sites, static PNGs from a generator are enough.
Why does View Source look fine but Facebook shows an old image?
Platform cache. Re-scrape after the HTML is correct. Astro itself is not the cache—Facebook or LinkedIn is.