惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
美团技术团队
博客园 - 三生石上(FineUI控件)
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
Microsoft Security Blog
Microsoft Security Blog
Y
Y Combinator Blog
A
About on SuperTechFans
The GitHub Blog
The GitHub Blog
U
Unit 42
H
Hackread – Cybersecurity News, Data Breaches, AI and More
云风的 BLOG
云风的 BLOG
IT之家
IT之家
MyScale Blog
MyScale Blog
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
I
InfoQ
博客园 - 司徒正美

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
OpenGraph, Twitter Cards, and article metadata in Astro
Roger Rajara · 2026-04-24 · via DEV Community

Roger Rajaratnam

Original post: OpenGraph, Twitter Cards, and article metadata in Astro

Series: Part 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:

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;

Enter fullscreen mode Exit fullscreen mode

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" />

Enter fullscreen mode Exit fullscreen mode

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} />
))}

Enter fullscreen mode Exit fullscreen mode

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} />

Enter fullscreen mode Exit fullscreen mode

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} />

Enter fullscreen mode Exit fullscreen mode

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:

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}
>

Enter fullscreen mode Exit fullscreen mode

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.