Skip to main content

Structured data & schema markup

What is structured data?

Structured data helps search engines understand the content of your pages better. You can add schema markup using JSON-LD by inserting it directly into the page. This lets the markup exist in the HTML sent from the server, which is important for SEO. Schema markup is a structured data format that helps search engines better understand your website’s content. If you are a cooking website, recipe schema can provide additional context to search engines about your recipes.

Why use structured data?

BenefitDescription
Rich SnippetsEnhanced search results with ratings, images, prices, and more
Better UnderstandingHelps Google understand your content context
Voice SearchStructured data powers voice assistant responses
Knowledge PanelsCan contribute to Google’s knowledge graph
Higher CTRRich results typically have higher click-through rates

Common schema types

Best for blog posts, news articles, and editorial content.
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Article Title",
  "author": { "@type": "Person", "name": "Author Name" },
  "datePublished": "2024-01-15",
  "image": "https://example.com/image.jpg"
}
For company or brand pages.
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Company Name",
  "url": "https://example.com",
  "logo": "https://example.com/logo.png"
}
For e-commerce and product pages.
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Product Name",
  "image": "https://example.com/product.jpg",
  "offers": {
    "@type": "Offer",
    "price": "99.99",
    "priceCurrency": "USD"
  }
}
For frequently asked questions pages.
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [{
    "@type": "Question",
    "name": "What is ButterCMS?",
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "ButterCMS is a headless CMS..."
    }
  }]
}

Implementing schema in Angular

In Angular, you can add schema markup using JSON-LD by inserting it directly into the page with Renderer2:
import { Component, OnInit, Renderer2, ElementRef } from '@angular/core';

@Component({
  selector: 'app-article',
  templateUrl: './article.component.html',
})
export class ArticleComponent implements OnInit {
  constructor(private renderer: Renderer2, private el: ElementRef) {}

  ngOnInit() {
    const schema = {
      "@context": "https://schema.org",
      "@type": "Article",
      "headline": "Angular SEO Guide",
      "author": {
        "@type": "Person",
        "name": "Your Name"
      },
      "datePublished": "2025-08-06"
    };

    const script = this.renderer.createElement('script');
    script.type = 'application/ld+json';
    script.text = JSON.stringify(schema);
    this.renderer.appendChild(this.el.nativeElement, script);
  }
}
This code adds schema.org structured data using JSON-LD inside a <script> tag. Search engines read this to understand the page content better.

Implementing schema in React/Next.js

For React and Next.js applications, you can create a reusable component for structured data:
// components/StructuredData.jsx
import Head from 'next/head';

export function StructuredData({ data }) {
  return (
    <Head>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
      />
    </Head>
  );
}

// Usage in a page component
import { StructuredData } from '../components/StructuredData';

export default function BlogPost({ post }) {
  const articleSchema = {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": post.seo_title || post.title,
    "author": {
      "@type": "Person",
      "name": `${post.author.first_name} ${post.author.last_name}`
    },
    "datePublished": post.published,
    "dateModified": post.updated,
    "image": post.featured_image,
    "description": post.meta_description
  };

  return (
    <>
      <StructuredData data={articleSchema} />
      {/* Rest of your component */}
    </>
  );
}

Using ButterCMS data for schema

When fetching content from ButterCMS, map your content fields to schema properties:

Blog Post article schema

// Fetch blog post from ButterCMS
const response = await butter.post.retrieve('my-post-slug');
const post = response.data.data;

// Create Article schema
const articleSchema = {
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": post.seo_title || post.title,
  "description": post.meta_description || post.summary,
  "image": post.featured_image,
  "author": {
    "@type": "Person",
    "name": `${post.author.first_name} ${post.author.last_name}`,
    "url": `/author/${post.author.slug}`
  },
  "publisher": {
    "@type": "Organization",
    "name": "Your Site Name",
    "logo": {
      "@type": "ImageObject",
      "url": "https://yoursite.com/logo.png"
    }
  },
  "datePublished": post.published,
  "dateModified": post.updated,
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": `https://yoursite.com/blog/${post.slug}`
  }
};

Page schema with SEO Component

// Fetch page from ButterCMS
const response = await butter.page.retrieve('landing_page', 'my-page-slug');
const page = response.data.data;

// Create WebPage schema using SEO component data
const pageSchema = {
  "@context": "https://schema.org",
  "@type": "WebPage",
  "name": page.fields.seo?.title || page.name,
  "description": page.fields.seo?.description,
  "image": page.fields.seo?.og_image,
  "url": `https://yoursite.com/${page.slug}`,
  "datePublished": page.published,
  "dateModified": page.updated
};

Collection items (e.g., FAQ schema)

// Fetch FAQ collection from ButterCMS
const response = await butter.content.retrieve(['faq']);
const faqs = response.data.data.faq;

// Create FAQPage schema
const faqSchema = {
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": faqs.map(faq => ({
    "@type": "Question",
    "name": faq.question,
    "acceptedAnswer": {
      "@type": "Answer",
      "text": faq.answer
    }
  }))
};

Creating a schema Component in ButterCMS

You can create a dedicated Structured Data component in ButterCMS to give content editors control over schema markup:
Structured Data Component:
├── schema_type (Dropdown: Article, Product, FAQ, Organization, etc.)
├── headline (Short Text)
├── description (Long Text)
├── image (Media)
├── author_name (Short Text)
├── date_published (Date)
├── date_modified (Date)
└── additional_properties (Long Text - JSON format)
Then in your frontend:
function generateSchemaFromComponent(schemaData) {
  const baseSchema = {
    "@context": "https://schema.org",
    "@type": schemaData.schema_type,
    "headline": schemaData.headline,
    "description": schemaData.description,
    "image": schemaData.image,
    "datePublished": schemaData.date_published,
    "dateModified": schemaData.date_modified
  };

  // Add author if provided
  if (schemaData.author_name) {
    baseSchema.author = {
      "@type": "Person",
      "name": schemaData.author_name
    };
  }

  // Merge additional properties if provided
  if (schemaData.additional_properties) {
    try {
      const additionalProps = JSON.parse(schemaData.additional_properties);
      return { ...baseSchema, ...additionalProps };
    } catch (e) {
      console.warn('Invalid JSON in additional_properties');
    }
  }

  return baseSchema;
}

Testing your structured data

Always validate your structured data before deploying to production.
ToolPurposeURL
Google Rich Results TestTest for rich snippet eligibilitysearch.google.com/test/rich-results
Schema Markup ValidatorValidate schema.org syntaxvalidator.schema.org
Google Search ConsoleMonitor schema performancesearch.google.com/search-console

Common validation errors

Watch out for these common structured data errors:
  • Missing required properties (e.g., image for Article)
  • Invalid date formats (use ISO 8601: YYYY-MM-DD)
  • URLs without proper protocol (https://)
  • Invalid JSON syntax