Back to blog
Engineering #Astro#SEO#JSON-LD

Astro Structured Data Guide 2026: JSON-LD for Rich Search Results

Structured data helps search engines understand your content. A practical guide to implementing JSON-LD schema in Astro for improved SEO and rich results.

14 min · January 26, 2026 · Updated January 27, 2026
Topic relevant background image

TL;DR

  • JSON-LD (JavaScript Object Notation for Linked Data) helps search engines understand your content’s meaning.
  • Google recommends JSON-LD as the preferred structured data format.
  • Rich results (stars, FAQs, breadcrumbs) can significantly increase click-through rates.
  • In Astro, add structured data via a <script type="application/ld+json"> tag in your head component.
  • Common schema types: WebSite, BlogPosting, Article, FAQ, Product, Organization.
  • Use the astro-seo-schema package for TypeScript type checking, or implement manually.
  • Always validate with Google’s Rich Results Test before deploying.

Why Structured Data Matters

Structured data helps search engines understand:

  • What your page is about
  • Who created it
  • When it was published/updated
  • How it relates to other content

This understanding enables:

  • Rich search results (stars, images, FAQs)
  • Better content indexing
  • Enhanced voice search answers
  • Knowledge panel inclusion

Rich Results Impact

Rich Result TypePotential CTR Increase
FAQ15-30%
How-to10-20%
Review stars20-35%
Breadcrumbs10-15%
Article info5-15%

JSON-LD Basics

Format

{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "How to Implement Structured Data",
  "author": {
    "@type": "Person",
    "name": "John Doe"
  },
  "datePublished": "2026-01-27",
  "description": "A complete guide to structured data."
}

Embedding in HTML

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "My Website",
  "url": "https://example.com"
}
</script>

Astro Implementation

Basic Component

Create a reusable component for structured data:

---
// src/components/StructuredData.astro
interface Props {
  data: Record<string, unknown>;
}

const { data } = Astro.props;

const jsonLd = {
  "@context": "https://schema.org",
  ...data,
};
---

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

Usage in Layouts

---
// src/layouts/BlogPost.astro
import StructuredData from '../components/StructuredData.astro';
import BaseLayout from './BaseLayout.astro';

interface Props {
  title: string;
  description: string;
  publishedAt: string;
  updatedAt?: string;
  author: string;
  image?: string;
}

const { title, description, publishedAt, updatedAt, author, image } = Astro.props;
const canonicalUrl = Astro.url.href;
---

<BaseLayout>
  <StructuredData
    slot="head"
    data={{
      "@type": "BlogPosting",
      headline: title,
      description: description,
      datePublished: publishedAt,
      dateModified: updatedAt || publishedAt,
      author: {
        "@type": "Person",
        name: author,
      },
      mainEntityOfPage: {
        "@type": "WebPage",
        "@id": canonicalUrl,
      },
      image: image ? [image] : undefined,
      publisher: {
        "@type": "Organization",
        name: "Your Company",
        logo: {
          "@type": "ImageObject",
          url: "https://example.com/logo.png",
        },
      },
    }}
  />
  
  <article>
    <slot />
  </article>
</BaseLayout>

Website Schema

Add to your base layout for site-wide information:

---
// src/components/WebsiteSchema.astro
const siteUrl = import.meta.env.SITE;
---

<script type="application/ld+json" set:html={JSON.stringify({
  "@context": "https://schema.org",
  "@type": "WebSite",
  name: "Your Site Name",
  description: "Your site description",
  url: siteUrl,
  publisher: {
    "@type": "Organization",
    name: "Your Company",
    logo: {
      "@type": "ImageObject",
      url: `${siteUrl}/logo.png`,
    },
  },
  potentialAction: {
    "@type": "SearchAction",
    target: {
      "@type": "EntryPoint",
      urlTemplate: `${siteUrl}/search?q={search_term_string}`,
    },
    "query-input": "required name=search_term_string",
  },
})} />

Schema Types

BlogPosting

For blog articles:

const blogPostingSchema = {
  "@type": "BlogPosting",
  headline: "Article Title",
  description: "Article description",
  image: ["https://example.com/image.jpg"],
  datePublished: "2026-01-27T08:00:00+00:00",
  dateModified: "2026-01-27T10:00:00+00:00",
  author: {
    "@type": "Person",
    name: "Author Name",
    url: "https://example.com/author",
  },
  publisher: {
    "@type": "Organization",
    name: "Publisher Name",
    logo: {
      "@type": "ImageObject",
      url: "https://example.com/logo.png",
    },
  },
  mainEntityOfPage: {
    "@type": "WebPage",
    "@id": "https://example.com/blog/article-slug",
  },
};

FAQ

For FAQ pages or sections:

const faqSchema = {
  "@type": "FAQPage",
  mainEntity: [
    {
      "@type": "Question",
      name: "What is structured data?",
      acceptedAnswer: {
        "@type": "Answer",
        text: "Structured data is a standardized format for providing information about a page and classifying the page content.",
      },
    },
    {
      "@type": "Question",
      name: "Why use JSON-LD?",
      acceptedAnswer: {
        "@type": "Answer",
        text: "JSON-LD is Google's recommended format because it's easy to implement and doesn't require changes to your HTML structure.",
      },
    },
  ],
};

Organization

For your company:

const organizationSchema = {
  "@type": "Organization",
  name: "Company Name",
  url: "https://example.com",
  logo: "https://example.com/logo.png",
  description: "What your company does",
  sameAs: [
    "https://twitter.com/yourcompany",
    "https://linkedin.com/company/yourcompany",
    "https://github.com/yourcompany",
  ],
  contactPoint: {
    "@type": "ContactPoint",
    email: "contact@example.com",
    contactType: "customer service",
  },
};

For navigation breadcrumbs:

const breadcrumbSchema = {
  "@type": "BreadcrumbList",
  itemListElement: [
    {
      "@type": "ListItem",
      position: 1,
      name: "Home",
      item: "https://example.com",
    },
    {
      "@type": "ListItem",
      position: 2,
      name: "Blog",
      item: "https://example.com/blog",
    },
    {
      "@type": "ListItem",
      position: 3,
      name: "Article Title",
      item: "https://example.com/blog/article-slug",
    },
  ],
};

Using astro-seo-schema

For TypeScript type checking:

npm install astro-seo-schema
---
// src/pages/blog/[slug].astro
import { Schema } from 'astro-seo-schema';

const post = await getEntry('blog', Astro.params.slug);
---

<Schema
  item={{
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    headline: post.data.title,
    description: post.data.description,
    datePublished: post.data.publishedAt,
    dateModified: post.data.updatedAt,
    author: {
      "@type": "Person",
      name: post.data.author,
    },
  }}
/>

Benefits:

  • TypeScript types for Schema.org vocabulary
  • Compile-time validation
  • Autocompletion in IDE

Dynamic Schema Generation

From Content Collections

---
// src/pages/blog/[slug].astro
import { getEntry } from 'astro:content';
import StructuredData from '../../components/StructuredData.astro';

const { slug } = Astro.params;
const post = await getEntry('blog', slug);
const { Content } = await post.render();

const canonicalUrl = new URL(`/blog/${slug}`, Astro.site);

const schema = {
  "@type": "BlogPosting",
  headline: post.data.title,
  description: post.data.description,
  image: post.data.heroImage?.url,
  datePublished: post.data.publishedAt,
  dateModified: post.data.updatedAt || post.data.publishedAt,
  author: {
    "@type": "Organization",
    name: "Your Company",
  },
  mainEntityOfPage: {
    "@type": "WebPage",
    "@id": canonicalUrl.href,
  },
};
---

<html>
  <head>
    <StructuredData data={schema} />
  </head>
  <body>
    <article>
      <Content />
    </article>
  </body>
</html>

Multiple Schema Types

You can include multiple schema types on one page:

---
import StructuredData from '../components/StructuredData.astro';

const schemas = [
  {
    "@type": "WebPage",
    name: "Page Title",
    // ...
  },
  {
    "@type": "BreadcrumbList",
    itemListElement: [/* ... */],
  },
  {
    "@type": "FAQPage",
    mainEntity: [/* ... */],
  },
];
---

{schemas.map(schema => (
  <StructuredData data={schema} />
))}

Validation and Testing

Google Rich Results Test

  1. Go to Google Rich Results Test
  2. Enter your URL or paste HTML
  3. Check for errors and warnings
  4. Preview how rich results will appear

Schema Markup Validator

  1. Go to Schema.org Validator
  2. Paste your JSON-LD
  3. Verify against Schema.org specifications

Common Errors

ErrorFix
Missing required propertyAdd the required field (e.g., author for BlogPosting)
Invalid date formatUse ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
URL doesn’t match pageEnsure mainEntityOfPage matches canonical URL
Image not accessibleUse absolute URLs for images

Implementation Checklist

Setup

  • Create StructuredData.astro component
  • Add to base layout or page templates
  • Configure site URL in astro.config.mjs

Core Schemas

  • WebSite schema on all pages
  • Organization schema on homepage
  • BlogPosting for blog articles
  • BreadcrumbList for navigation

Optional Schemas

  • FAQ for FAQ pages/sections
  • Product for product pages
  • HowTo for tutorial content
  • Event for event pages

Testing

  • Validate with Rich Results Test
  • Check in Google Search Console
  • Monitor for crawl errors
  • Review rich results appearance

FAQ

Do I need structured data for SEO?

Structured data isn’t a direct ranking factor, but it enables rich results that can significantly increase click-through rates. It also helps search engines understand your content better.

Can I have multiple schema types on one page?

Yes. You can have WebPage + BreadcrumbList + FAQ + Article all on the same page. Just include separate JSON-LD blocks for each.

Should I use JSON-LD or Microdata?

Google recommends JSON-LD. It’s easier to implement (doesn’t require HTML changes) and easier to maintain.

How do I know if my structured data is working?

Check Google Search Console > Enhancements. It shows which schema types are detected and any errors. Rich results may take weeks to appear.

Does structured data work for all sites?

Structured data is detected by all major search engines. Rich results availability varies by search engine and schema type.

How often should I update structured data?

Update whenever the underlying content changes (dates, authors, descriptions). The schema should always reflect the actual page content.

Sources & Further Reading

Interested in our research?

We share our work openly. If you'd like to collaborate or discuss ideas — we'd love to hear from you.

Get in Touch

Let's build
something real.

No more slide decks. No more "maybe next quarter".
Let's ship your MVP in weeks.

Start Building Now