One-off tip

Support checkout

  1. 1 Choose amount
  2. 2 Payment
  3. 3 Thank you

Choose amount

Pick the level of support that feels right. You can keep it simple or enter a custom amount, then continue to secure payment.

Choose a one-off amount
OpenGraph, Twitter Cards, and article metadata in Astro
Cover image: 3D render of social media platform logos — Photo by Mariia Shalabaieva on Unsplash
astro engineering frontend

OpenGraph, Twitter Cards, and article metadata in Astro

Canonical URLs, OG images, article:published_time, and why it all lives in BaseLayout

Published
23 April 2026
Read time
4 min read
SeriesPart of How this blog was built — documenting every decision that shaped this site.

When someone shares a link to a blog post, the card that appears in Slack, LinkedIn, or iMessage is determined by OpenGraph tags in the <head>. Get them wrong and the shared link is an unformatted URL. Get them right and it shows the post title, description, and a properly sized cover image.

This is table stakes for any public-facing blog, but the implementation details matter — specifically, how to handle different content types (articles vs. pages), where to put canonical URLs, and how to avoid the common mistake of sharing a relative image path that produces a broken card.

Centralising metadata in BaseLayout

All meta tags are defined once in BaseLayout.astro. Every page passes what it needs as props; the layout handles the markup. This avoids duplication and ensures no page accidentally skips essential tags:

src/layouts/BaseLayout.astro
interface Props {
pageTitle: string;
description?: string;
ogImage?: string;
ogType?: "website" | "article";
canonicalUrl?: string;
pubDate?: Date;
author?: string;
tags?: string[];
}
const {
pageTitle,
description = "Practical software engineering guidance from Roger Rajaratnam for people breaking into tech, engineers growing in confidence, and teams improving engineering practice.",
ogImage = "/og-image.png",
ogType = "website",
canonicalUrl = Astro.url.href,
pubDate,
author,
tags,
} = Astro.props;

The default description covers general pages. The default ogType is "website". Both are overridden for posts. The default canonicalUrl is Astro.url.href — the fully qualified URL including the site’s site value from astro.config.mjs.

The OpenGraph block

<!-- OpenGraph -->
<meta property="og:type" content={ogType} />
<meta property="og:site_name" content={siteName} />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:image" content={new URL(ogImage, Astro.site ?? Astro.url.origin).href} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:locale" content="en_GB" />

The og:image value deserves attention. A relative path like /og-image.png won’t work in OG tags — social crawlers need an absolute URL. Constructing it with new URL(ogImage, Astro.site) handles both cases: if ogImage is already absolute it passes through unchanged; if it’s a root-relative path it’s resolved against the site’s configured base URL.

siteName is a plain constant — const siteName = "Sourcier" — defined at the top of BaseLayout.astro. og:locale uses the IETF language tag for British English, which matches the site’s target audience.

1200×630 is the recommended OG image size that renders well across Facebook, LinkedIn, and Slack.

Article-specific tags

When ogType is "article", the Open Graph protocol defines additional properties for structured article metadata. These are conditionally rendered:

{pubDate && (
<meta
property="article:published_time"
content={pubDate.toISOString()}
/>
)}
{author && <meta property="article:author" content={author} />}
{tags && tags.map((tag) => (
<meta property="article:tag" content={tag} />
))}

article:published_time uses the ISO 8601 format with timezone — Date.toISOString() provides this. article:tag can appear multiple times, once per tag. Some scrapers and indexers use these to understand content type and category.

Twitter Cards

X (formerly Twitter) has its own metadata system that runs parallel to OpenGraph. The summary_large_image card type displays the image at full width above the title and description:

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={pageTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={new URL(ogImage, Astro.site ?? Astro.url.origin).href} />

The property names keep the twitter: prefix — these are a stable protocol standard and won’t change regardless of the platform rebrand. X falls back to OpenGraph values for some properties, but it’s more reliable to specify them explicitly. The image URL construction is the same as for OG.

Canonical URLs

<link rel="canonical" href={canonicalUrl} />

The canonical tag tells search engines which URL is the authoritative version of a page, which matters if content appears at multiple URLs or is syndicated elsewhere. canonicalUrl defaults to Astro.url.href so it’s correct without any manual input, but can be overridden for pages that need a different canonical (for example, a paginated page that canonicalises to page 1).

Passing metadata from post pages

MarkdownPostLayout.astro extracts the relevant frontmatter fields and passes them to BaseLayout:

src/layouts/MarkdownPostLayout.astro
const ogImage = frontmatter.cover?.image?.src ?? undefined;
<BaseLayout
pageTitle={`${frontmatter.title} — Sourcier`}
description={frontmatter.description}
ogImage={ogImage}
ogType="article"
pubDate={frontmatter.pubDate}
author={frontmatter.author}
tags={frontmatter.tags}
>

The ogImage falls back to undefined if no cover is present, which means BaseLayout will use the default /og-image.png for posts without cover images.

You can browse the rest of the site code in the web-sourcier.uk repository.

Working on something similar?

Need help raising the bar?

I help teams improve engineering practice through hands-on delivery, pragmatic reviews, and mentoring. If you want a second pair of eyes or practical support, let's talk.

  • Engineering practice review
  • Hands-on delivery
  • Team mentoring
Get guidance

If this has been useful, you can back the writing with a one-off tip through a secure Stripe checkout.

Comments

Loading comments…

Leave a comment

Free · Practical · One email per post

Get practical engineering notes

One short email when a new article goes live. Useful if you are breaking into tech, growing as an engineer, or improving engineering practice on your team.