Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 42

Implementing Structured Data for SEO

Recommended resources

Hello! In our last lesson, you learned how to automatically generate an XML sitemap. This provides search engines with a "roadmap" of all the URLs on your site, ensuring they can find all your content. You've told them where to look. Now, it's time to tell them what your content is about in a much richer, more structured way.

This lesson continues our journey through the SEO and Web Accessibility module. Our goal is to implement structured data (JSON-LD) for your blog posts and services. This is a powerful SEO technique that helps search engines understand the context of your content, making your site eligible for "rich results" in search listings—think of article carousels, FAQ dropdowns, or service details appearing directly on the Google results page. For a professional accounting site, this adds a layer of authority and can significantly improve click-through rates.

Part 1: What is Structured Data?

Before we dive into the code, let's clarify what structured data is and why it's so valuable. At its core, it's a standardized format for providing explicit clues about the meaning of a page. While a sitemap lists URLs, structured data describes the content at those URLs.

The following short video from Google provides an excellent overview of what structured data is, how it helps search engines, and why JSON-LD is the recommended implementation format.

Structured data for web developers

Watch this video from the Google Search Central channel to get a foundational understanding of structured data from the source.

Watch from the beginning to 03:01, and then from 04:02 to 04:37. Focus on these key points: How structured data enables richer search features. The role of schema.org as a shared vocabulary. Why JSON-LD is the simplest and recommended format to use.

To summarize the key points from the video:

  • Structured Data: A machine-readable format that explicitly describes your page's content.
  • Schema.org: A collaborative project that provides a shared vocabulary (or "schema") for different types of content (articles, events, products, services, etc.).
  • JSON-LD: The specific syntax Google recommends for embedding this structured data on your site. It's a block of JSON in a <script> tag, which keeps it neatly separated from your page's visible HTML content. Your JavaScript background will make this format feel very familiar.

Part 2: Implementing Structured Data in Astro

The best way to manage structured data in Astro is to create reusable components that generate the JSON-LD script dynamically. This approach is clean, scalable, and fits perfectly with the component-based architecture you're already using.

The blog post "How I add structured data markup to Astro sites" by Stephen Lunt provides an excellent, practical guide for this. We'll be following its core strategy.

How I add structured data markup to Astro sites

This article details a robust pattern for adding structured data to an Astro site using content collections and component slots. It's the primary guide for our implementation.

First, read the sections 'What is structured data?' and 'Example Astro structured data repository'. Pay close attention to how a named <slot name="structured-data" /> is added to the main layout's <head>. This is the key to injecting our data cleanly.

Step 1: Update Your Base Layout

Following the pattern from the article, the first step is to create a placeholder in your site's <head> where the structured data will be injected.

Open your base layout file (likely src/layouts/Layout.astro) and add a named <slot /> inside the <head> tag. A good place is right after your <SEO /> component.

// src/layouts/Layout.astro
---
import SEO from '../components/SEO.astro';
// ... other imports
interface Props {
  // ... your props
}
const { title, description } = Astro.props;
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- ... other head elements -->
    <SEO title={title} description={description} />
    <slot name="structured-data" />  <!-- Add this line -->
  </head>
  <body>
    <slot /> <!-- This is your main content slot -->
  </body>
</html>

This <slot> acts as a portal. Any component rendered with the attribute slot="structured-data" will now appear in the <head> of the page.

Step 2: Create an Article Schema Component for Blog Posts

Now, let's create a component that generates the structured data for a blog post. We'll use the Article schema, which is perfect for this.

The article by Stephen Lunt provides a complete code example for this component. Let's study it and adapt it for our project.

How I add structured data markup to Astro sites

Now, let's focus on the practical implementation for blog posts.

Read the 'Article structured data' section carefully. This contains the full source code for an ArticleStructuredData.astro component and shows how to use it in a dynamic blog post page. Notice how it imports CollectionEntry for type safety and uses props from the post's frontmatter to build the schema object.

Based on that example, create a new file: src/components/ArticleSchema.astro.

// src/components/ArticleSchema.astro
---
import type { CollectionEntry } from "astro:content";

// Define Props to accept the data object from a 'blog' collection entry
type Props = CollectionEntry<"blog">["data"];

const { title, description, pubDate, updatedDate, author, heroImage } = Astro.props;

// Ensure you have `site` set in your astro.config.mjs
const canonicalURL = new URL(Astro.url.pathname, Astro.site);

const structuredData = {
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": title,
  "description": description,
  "datePublished": pubDate.toISOString(),
  "dateModified": updatedDate ? updatedDate.toISOString() : pubDate.toISOString(),
  "image": new URL(heroImage, Astro.site).href,
  "url": canonicalURL.href,
  "author": {
    "@type": "Person",
    "name": author, // Assuming 'author' in your frontmatter is a simple string for now
    // You could expand this with a URL to an author page, etc.
  },
  "publisher": {
    "@type": "Organization",
    "name": "Your Accounting Firm Name", // Replace with your client's firm name
    "logo": {
      "@type": "ImageObject",
      "url": new URL("/logo.png", Astro.site).href, // Replace with your logo path
    },
  },
};
---

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

Note: This code assumes your blog content collection schema has fields like title, description, pubDate, author, and heroImage. Adjust the property names to match your Zod schema in src/content/config.ts.

Step 3: Use the Component in Your Blog Post Template

Now, open your dynamic blog post page (e.g., src/pages/blog/[...slug].astro) and use the new component.

// src/pages/blog/[...slug].astro
---
import { getCollection, type CollectionEntry } from 'astro:content';
import Layout from '../../layouts/Layout.astro';
import ArticleSchema from '../../components/ArticleSchema.astro'; // Import the component

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map(post => ({
    params: { slug: post.slug },
    props: post,
  }));
}

type Props = CollectionEntry<'blog'>;
const post = Astro.props;
const { Content } = await post.render();
---
<Layout title={post.data.title} description={post.data.description}>
  <!-- Inject the structured data into the 'head' via the slot -->
  <ArticleSchema slot="structured-data" {...post.data} />

  <article>
    <h1>{post.data.title}</h1>
    <img src={post.data.heroImage} alt="" />
    <Content />
  </article>
</Layout>

And that's it! Every blog post generated from your content collection will now automatically have a perfectly formatted Article JSON-LD block in its HTML <head>, with all the data pulled directly from its frontmatter. This is a highly maintainable and scalable solution.


Part 3: Creating a Service Schema Component

The process for your services pages is identical—we just need to use a different schema and create a corresponding component. For an accounting firm, the Service schema is a great choice.

  1. Create the Component: Create a new file at src/components/ServiceSchema.astro.
  2. Define the Schema: Inside this component, define a Service schema. You'll pull data from your services content collection.
  3. Use It: In your dynamic service page template (e.g., src/pages/services/[...slug].astro), import and use the ServiceSchema component in the structured-data slot.

Here's an example of what src/components/ServiceSchema.astro might look like.

// src/components/ServiceSchema.astro
---
import type { CollectionEntry } from "astro:content";

// Assumes you have a 'services' content collection
type Props = CollectionEntry<"services">["data"];

const { title, description } = Astro.props;

const canonicalURL = new URL(Astro.url.pathname, Astro.site);

const structuredData = {
  "@context": "https://schema.org",
  "@type": "Service",
  "name": title,
  "description": description,
  "url": canonicalURL.href,
  "provider": {
    "@type": "Organization", // or "AccountingService" for more specificity
    "name": "Your Accounting Firm Name",
    "logo": new URL("/logo.png", Astro.site).href,
  },
  "serviceType": "Tax Preparation", // This could be a prop from your frontmatter
  // You can also add `areaServed`, `audience`, etc.
};
---

<script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
Test your understanding!

Imagine one of your accounting services is "Bookkeeping for Small Businesses". Based on the Service schema, what are three relevant properties you could add to the frontmatter of your services MDX file to make the structured data more descriptive?

Show answer

Here are a few great options you could add to your frontmatter and then use in your ServiceSchema.astro component:

  1. serviceType: "Bookkeeping". This is more specific than just "Service".
  2. audience: You could define an audience object with @type: "Audience" and audienceType: "Small Businesses". This tells search engines exactly who the service is for.
  3. areaServed: If the service is for a specific geographic location, like "New York", you can specify it. This is great for local SEO.
  4. hasOfferCatalog: You could link to a catalog of pricing tiers.

Adding these would make your frontmatter richer and the resulting JSON-LD much more useful to search engines.


Part 4: Validating Your Implementation

Adding the code is only half the battle. You must verify that search engines can correctly parse your structured data. Two essential tools for this are:

  1. Schema.org Markup Validator: Checks against the official schema.org specifications.
  2. Google Rich Results Test: Checks if your data is valid specifically for Google's rich result features.

The following resource provides links to these tools and explains their importance.

How I add structured data markup to Astro sites

Finally, let's ensure our implementation is correct by validating it.

Read the section 'More structured data tutorials and testing'. Follow the links to the Schema.org validator and the Rich Results Test and bookmark them. These are indispensable tools for SEO work.

To use these tools:

  1. Run npm run build to build your site with the new structured data components.
  2. Run npm run preview to serve the built site locally.
  3. Open the Rich Results Test, select "URL", and enter the local URL of a blog post or service page (e.g., http://localhost:4321/blog/my-first-post).
  4. The tool will analyze the page and show you what structured data it found and whether there are any errors or warnings. Fix any issues it reports.

Conclusion

Congratulations! You've just implemented one of the most impactful technical SEO features. By providing machine-readable structured data, you're not just helping search engines categorize your content—you're making your site eligible for enhanced visibility right on the search results page. Your component-based approach in Astro ensures this will scale effortlessly as you add dozens of blog posts and services.

Key Takeaways:

  • Structured data adds explicit, machine-readable context to your web pages using the schema.org vocabulary.
  • JSON-LD is the Google-recommended format for implementation, which involves adding a <script> tag with a JSON object to your page's <head>.
  • In Astro, the best practice is to create reusable components (e.g., ArticleSchema.astro, ServiceSchema.astro) that dynamically generate the JSON-LD from content collection frontmatter.
  • Using a named <slot name="structured-data" /> in your base layout provides a clean way to inject this data into the <head> of every page.
  • Always validate your implementation using tools like the Google Rich Results Test to ensure it's error-free.

Up Next:

We've focused on communicating with machines for SEO. In the next lesson, we will shift our focus slightly to humans using assistive technologies. You will learn how to structure pages with semantic HTML (landmarks, headings), a foundational practice for web accessibility (WCAG). This not only helps screen reader users navigate your site but also reinforces the content hierarchy for search engines, tying our SEO and accessibility efforts together.