Schema Markup in Astro Sites (JSON-LD for Static, SSR, and MDX)
Table of Contents
Astro doesn't generate schema markup automatically — but its component model makes adding JSON-LD trivial. Whether you're using Astro's static mode, SSR, MDX content collections, or hybrid rendering, the same JSON-LD pattern works. This guide gives you copy-paste examples for the most common Astro setups, with patterns that scale from a few pages to thousands.
The Basic Astro Schema Pattern
Astro components can render any HTML, including script tags with JSON-LD. The simplest pattern: build a JavaScript object in the component's frontmatter, then render it inside a script tag.
---
const schema = {
"@context": "https://schema.org",
"@type": "Article",
"headline": "Your Article Title",
"datePublished": "2026-04-08",
"author": { "@type": "Person", "name": "Author Name" }
};
---
<script type="application/ld+json" set:html={JSON.stringify(schema)}></script>
The set:html directive tells Astro to inject the JSON string as raw HTML without escaping it. Without set:html, Astro would escape the quotes and break your JSON.
This pattern works in any .astro file — page, layout, or component.
Build a Reusable JsonLd Component
For sites with many pages, create a reusable component that takes a schema object as a prop:
---
// src/components/JsonLd.astro
const { schema } = Astro.props;
---
<script type="application/ld+json" set:html={JSON.stringify(schema)}></script>
Then use it from any page:
---
// src/pages/blog/example.astro
import JsonLd from '../../components/JsonLd.astro';
const articleSchema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "Example Post",
"datePublished": "2026-04-08"
};
---
<Layout>
<JsonLd schema={articleSchema} />
<article>...</article>
</Layout>
The JsonLd component is reusable across all your pages with different schema objects passed in. This scales much better than copy-pasting script tags into every page.
Sell Custom Apparel — We Handle Printing & Free ShippingSchema for Content Collections
Astro's content collections let you query MDX or markdown files at build time. To add schema dynamically based on collection data, build the schema object from the entry data:
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
import JsonLd from '../../components/JsonLd.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post }
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
const schema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": post.data.title,
"description": post.data.description,
"datePublished": post.data.publishedDate.toISOString(),
"author": { "@type": "Person", "name": post.data.author },
"image": post.data.heroImage
};
---
<Layout>
<JsonLd schema={schema} />
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
</Layout>
Now every blog post gets its own Article schema automatically, populated from the post's frontmatter. Add a new post and it gets schema for free.
Site-Wide Schema in the Layout
For Organization and WebSite schema that should appear on every page, add it to your shared layout component:
---
// src/layouts/BaseLayout.astro
import JsonLd from '../components/JsonLd.astro';
const orgSchema = {
"@context": "https://schema.org",
"@type": "Organization",
"name": "Your Brand",
"url": "https://yoursite.com",
"logo": "https://yoursite.com/logo.png",
"sameAs": [
"https://twitter.com/yourbrand",
"https://linkedin.com/company/yourbrand"
]
};
---
<html>
<head>
<JsonLd schema={orgSchema} />
</head>
<body>
<slot />
</body>
</html>
This schema renders on every page that uses BaseLayout. Pages can additionally include their own page-specific schema (Article, Product, FAQ) without conflict.
MDX-Specific Patterns
If your content lives in MDX files (rather than separate Astro pages), you can include JSON-LD directly in the MDX file using a JsonLd component import:
---
title: "My MDX Post"
description: "Example MDX post with schema"
---
import JsonLd from '../../components/JsonLd.astro';
<JsonLd schema={{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "My MDX Post",
"datePublished": "2026-04-08"
}} />
# My MDX Post
Content goes here...
This is more flexible than static page schema because each MDX file controls its own schema. The downside: you have to remember to include it in every MDX file. The collection-based pattern (described above) is usually cleaner because it generates schema automatically from frontmatter.
For more on the validation step, see our validator workflow guide.
Try It Free — No Signup Required
Runs 100% in your browser. No data is collected, stored, or sent anywhere.
Open Free Schema Markup GeneratorFrequently Asked Questions
Why do I need set:html in Astro JSON-LD?
Without set:html, Astro escapes special characters in the script content (quotes, ampersands), which breaks the JSON. set:html tells Astro to render the string as raw HTML without escaping. It's the Astro equivalent of React's dangerouslySetInnerHTML.
Does Astro generate schema automatically?
No. Astro is unopinionated about schema markup — it gives you the tools to add it but doesn't generate any by default. You add schema via components, collections, or layout templates.
Should schema go in the layout or the page component?
Both. Site-wide schema (Organization, WebSite) goes in the shared layout. Page-specific schema (Article, Product, FAQ) goes in the individual page or content collection template. They render together in the final HTML.
Will schema work in Astro's SSR mode?
Yes. The same JsonLd component works in both static and SSR modes. In SSR, the schema is rendered on each request from server data. In static mode, it's rendered once at build time. Output is identical.
Can I generate schema dynamically from CMS data?
Yes. Fetch the data in the page's frontmatter (using fetch() or your CMS SDK), then build the schema object from that data and pass to JsonLd. Works with Contentful, Sanity, Strapi, Notion, or any headless CMS Astro supports.
Should I use a third-party Astro schema package?
Not necessary. The pattern is so simple (one component, JSON.stringify, set:html) that wrapping it in a package adds dependency overhead without much value. Write the JsonLd component yourself in 5 lines of code.

