When we talk about SEO, we often think of keywords, <h1> tags, or meta descriptions. They are essential, but today, search engines want more: they want context.

How does Google know your page is a “Blog Post” written by a specific “Person”, published on a precise “Date”, rather than just a random page of text?

The answer is in six letters: JSON-LD (JavaScript Object Notation for Linked Data).

The Problem: Letting Google Guess

Without structured data, web crawlers have to parse your HTML and “guess” what’s important. Sometimes they get it right, often they get it wrong.

With JSON-LD, there is no more room for doubt. We inject a small script, invisible to the user, but which explicitly tells the bots: “This is a technical blog post, here is the exact title, the URL of the cover image, and the author’s identity.”

The bonus? This is what allows you to get those famous Rich Snippets in the search results.

Automation with Astro

Writing this JSON by hand for every article would be a nightmare. Fortunately, the power of Astro components allows us to automate this completely.

1. Preparing the BaseHead

If you followed my previous articles on this blog’s architecture, you know I have a BaseHead.astro component that manages my entire <head>.

We just need to tell it to accept a new schema property and inject it if it exists:

---
// src/components/BaseHead.astro
const { title, description, image, schema } = Astro.props;

// ... (OpenGraph image URLs logic seen previously)
---

<title>{title}</title>
<meta name="description" content={description} />

{schema && (
  <script type="application/ld+json" set:html={JSON.stringify(schema)} />
)}

Note: The set:html attribute is crucial here. It tells Astro to inject the raw string into the script tag without escaping it.

2. Generating the Schema in the Article Layout

Now, let’s go into the layout that handles our blog posts (src/layouts/BlogPost.astro). This is where we have all the data: title, date, social image, etc.

We are going to build a JavaScript object following the Schema.org standard for the BlogPosting type.

---
// src/layouts/BlogPost.astro
import BaseHead from '../components/BaseHead.astro';
const { frontmatter, slug } = Astro.props;

// Our dynamically generated image URL (Satori) adapted for English
const ogImageUrl = new URL(`/en/og/${slug}.png`, Astro.site).href;
const articleUrl = new URL(Astro.url.pathname, Astro.site).href;

// 🧠 Building the JSON-LD Schema
const schema = {
  "@context": "[https://schema.org](https://schema.org)",
  "@type": "BlogPosting",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": articleUrl
  },
  "headline": frontmatter.title,
  "description": frontmatter.description,
  "image": [ogImageUrl],
  "datePublished": frontmatter.pubDate,
  // Add dateModified if you track updates!
  "author": {
    "@type": "Person",
    "name": "Valentin Besse",
    "url": "[https://vbesse.com](https://vbesse.com)"
  }
};
---

<html lang="en">
  <head>
    <BaseHead 
      title={frontmatter.title} 
      description={frontmatter.description} 
      schema={schema} 
    />
  </head>
  <body>
    <slot />
  </body>
</html>

The Ultimate Test

Once this code is in place, run a build of your site and open the source code of an article. You will see your beautiful JSON-LD script ready to be devoured by Googlebot.

To be 100% sure your syntax is perfect, copy your article’s URL (or the generated source code) and paste it into the Google Rich Results Test.

If everything is green, congratulations: you are fluent in the language of search engines.