JSON-LD Structured Data for Blogs: A Real Implementation
Rich results achieve 82% higher click-through rates compared to standard search listings (Search Engine Journal, 2025 🔗). But in 2026, structured data is no longer just about rich snippets. It is how AI systems like Google AI Overviews, ChatGPT, and Perplexity decide whether your content is worth citing. According to Semrush’s 2025 AI Overviews study 🔗, AI Overviews now appear in over 15% of Google searches, and pages with clear semantic structure are significantly more likely to be cited in those results.
I just added JSON-LD structured data to this very blog. In this post, I will walk through the exact code, the architectural decisions, and the verification steps. No toy examples, just production-ready implementation. If you care about HTTP caching headers or ETags for content freshness, you already understand the value of giving browsers and crawlers explicit signals. JSON-LD structured data does the same thing for search engines and AI systems.
What Is JSON-LD and Why Does Google Recommend It?
JSON-LD (JavaScript Object Notation for Linked Data) is a method of encoding structured data using JSON. Google explicitly states in their developer documentation 🔗: “Google recommends using JSON-LD for structured data whenever possible.”
There are three formats for structured data on the web:
- Microdata: attributes woven into your HTML elements. Tightly coupled to your markup, hard to maintain.
- RDFa: similar to Microdata but more verbose. Used by some CMS platforms.
- JSON-LD: a standalone
<script>block in your<head>. Completely decoupled from your HTML.
JSON-LD wins because it lives separately from your content. You can add, remove, or modify your structured data without touching a single line of your page’s HTML. For component-based frameworks like Astro, React, or Next.js, this is the natural choice. Your schema is just another data object.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Your Article Title",
"datePublished": "2026-04-07T00:00:00.000Z"
}
</script>
Which BlogPosting Fields Actually Matter?
The schema.org BlogPosting type 🔗 has dozens of properties, but not all of them are equal. Here is what Google actually uses for rich results and what AI systems look for when deciding whether to cite you.
Required for rich results:
headline: your article titledatePublished: ISO 8601 formatauthor: aPersonorOrganizationwith at least aname
Strongly recommended:
dateModified: signals freshness. 76% of top AI-cited content was updated within 30 days.description: helps AI systems understand the article’s scopeimage: enables visual rich resultsurl: canonical URL of the article
Good to have:
publisher: for E-E-A-T signals. Can be the same asauthorfor personal blogs.
Do not over-engineer it. A clean schema with the right fields beats a bloated one with properties Google ignores.
How Do You Build a JSON-LD Structured Data Component in Astro?
Here is the actual component I built for this blog. It lives at src/components/seo/json-ld.astro:
---
export interface Props {
title: string;
description: string;
url: string;
image?: string;
datePublished: string;
dateModified?: string;
author?: string;
}
const {
title,
description,
url,
image,
datePublished,
dateModified,
author = "Francesco Di Donato",
} = Astro.props;
const schema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: title,
description,
url,
...(image && { image }),
datePublished,
...(dateModified && { dateModified }),
author: {
"@type": "Person",
name: author,
url: "https://didof.dev",
},
publisher: {
"@type": "Person",
name: author,
url: "https://didof.dev",
},
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)} />
Three things worth noting:
-
Conditional spreading:
...(image && { image })only includes theimagefield when one exists. Same fordateModified. This avoidsnullorundefinedvalues in your JSON-LD, which can trigger validation warnings. -
The
set:htmldirective: Astro’sset:htmlsafely injects the JSON string into the script tag. Never use template literals for this.set:html={JSON.stringify(schema)}handles escaping correctly and prevents XSS vectors if any prop contains user-controlled data. -
Typed Props: the
Propsinterface ensures TypeScript catches missing required fields at build time, not in production.
How Do You Wire JSON-LD Into Your Layout Conditionally?
The key architectural decision: JSON-LD should only render on blog posts, not on every page. The homepage, about page, and tools pages do not need BlogPosting schema.
In src/layouts/main.astro, I added an optional schema prop:
export interface Props {
title?: string;
description?: string;
image?: string;
footer?: boolean;
header?: boolean;
disableFloatingCal?: boolean;
schema?: {
datePublished: string;
dateModified?: string;
};
}
Then in the <head>, the component renders conditionally:
{schema && (
<BlogJsonLd
title={title || "didof.dev"}
description={description}
url={canonicalURL.toString()}
image={new URL(image, Astro.site).toString()}
datePublished={schema.datePublished}
dateModified={schema.dateModified}
/>
)}
The blog post page (src/pages/[locale]/blog/[...slug].astro) passes the schema data from the content collection:
<Main
title={post.data.title}
description={post.data.description}
image={ogImage?.src}
schema={{
datePublished: post.data.date.toISOString(),
dateModified: post.data.update_date?.toISOString(),
}}
>
Every blog post now gets structured data automatically. No manual JSON-LD per post, no forgetting to add it. If update_date is not set in the frontmatter, dateModified simply does not appear in the schema. Exactly the behavior we want.
How Do You Verify JSON-LD Structured Data Is Working?
After building, you can verify JSON-LD is present in the output:
# Check that blog posts contain JSON-LD
grep -l "application/ld+json" dist/en/blog/*/index.html | head -5
# Inspect the actual schema for one post
grep -o '<script type="application/ld+json">[^<]*</script>' \
dist/en/blog/setup-n8n-and-searxng-locally/index.html # any post works
The output should be valid JSON with @type: "BlogPosting", your headline, datePublished, and author fields.
For production validation, use two tools:
- Schema Markup Validator 🔗: paste your URL to check for schema.org compliance
- Google Rich Results Test 🔗: shows what Google can extract and whether you qualify for rich results
Why Does JSON-LD Structured Data Matter for AI Search in 2026?
Structured data is no longer optional SEO polish. It is infrastructure for AI visibility.
When Google AI Overviews, ChatGPT web search, or Perplexity crawl your page, they are trying to answer three questions:
- What is this content about?
headline,description,@type - Is it trustworthy?
author,publisher,datePublished,dateModified - Is it current?
dateModifiedis the strongest freshness signal
Without structured data, AI systems have to guess these answers from your HTML. With it, you are handing them the answers directly, in a format they are designed to consume.
JSON-LD structured data can increase your click-through rate by 35% even without a visual rich result, because search engines understand your content better and rank it more confidently. If you are building tools that interact with APIs, like setting up n8n and SearXNG locally, adding structured data helps those pages get discovered by the same AI systems you are building with.
FAQ
Do I need a separate JSON-LD block for each blog post?
No. With the component approach shown above, every blog post automatically gets its own JSON-LD block generated from the content collection data. You write the component once and it scales to every post.
Does JSON-LD affect page performance?
No measurable impact. The JSON-LD script block is a few hundred bytes of text. It does not execute as JavaScript. The browser ignores type="application/ld+json" scripts entirely. Only search engines and AI crawlers parse them.
Should I use the astro-seo-schema npm package instead?
It depends. The astro-seo-schema package provides TypeScript definitions from schema-dts, which gives you autocomplete for every schema.org property. For a simple BlogPosting schema, a hand-rolled component like the one above is lighter and gives you full control. For complex schemas with multiple nested types, the package can help prevent mistakes.
What about FAQ schema on this very page?
Google deprecated HowTo rich results in September 2023, but FAQPage schema remains supported. Google now shows FAQ rich results less frequently though. The value of FAQ sections in 2026 is primarily for AI citation: question-answer formats are the easiest structure for AI systems to extract and cite.