title: JSON-LD schema with GatsbyJS for rich snippets
body: <p>I have a basic blog setup with Gatsby and at the time of posting this question there <a href="https://www.gatsbyjs.org/docs/add-seo-component/" rel="nofollow noreferrer">lacks good documentation for SEO components</a>. There are examples of basic SEO components but what I am wanting is a little more in-depth. Maybe, if a solution is reached here it could be contributed to the Gatsby docs for others to benefit.</p>
  <p>On top of the usual title and description meta tags and the facebook/twitter open graph meta (which I have done already), I want to add structured data for <a href="https://developers.google.com/search/docs/guides/search-gallery" rel="nofollow noreferrer">rich snippets</a> which will vary depending on what the blog post type is. For example, I might have a regular post which would print <a href="https://developers.google.com/search/docs/data-types/article" rel="nofollow noreferrer">Article</a> schema, some posts might be <a href="https://developers.google.com/search/docs/data-types/how-to" rel="nofollow noreferrer">How-to</a>, in which case I'd like to print HowTo schema instead of Article. At some point I might write a post with would suit <a href="https://developers.google.com/search/docs/data-types/faqpage" rel="nofollow noreferrer">FAQ</a> schema.</p>
  <p>I don't know if this is the best approach but here's what I'm thinking:</p>
  <h3>1. In frontmatter set the schema type I want to true, leave the rest false.</h3>
  <p>I am also thinking of storing the schema data in the frontmatter but as this data is quite complex and will vary from post type to post type (Article, HowTo etc.) I'm not sure if this is yet a good idea?</p>
  <pre><code>---
  title: Hello World
  description: How to say hello
  article: false
  how-to: true
  faq: false
  ---
  </code></pre>
  <h3>2. Test for true/false in the SEO component and print the correct schema.</h3>
  <p>Below is my entire SEO component, which obviously doesn't work but you can hopefully see where my thinking is headed. I have dissected and borrowed from the <a href="https://github.com/Vagr9K/gatsby-advanced-starter/blob/master/src/components/SEO/SEO.jsx" rel="nofollow noreferrer">gatsby advanced starter component</a> and the <a href="https://github.com/LekoArts/gatsby-starter-prismic/blob/master/src/components/SEO/SEO.jsx" rel="nofollow noreferrer">gatsby starter prismic component</a> but neither do quite what I need. Here's mine:</p>
  <pre><code>import React from &quot;react&quot;
  import Helmet from &quot;react-helmet&quot;
  import PropTypes from &quot;prop-types&quot;
  import { useStaticQuery, graphql } from &quot;gatsby&quot;
  import Facebook from &quot;./Facebook&quot;
  import Twitter from &quot;./Twitter&quot;
  
  const SEO = ({
    title,
    desc,
    banner,
    pathname,
    published,
    modified,
    article,
    webpage,
    node,
  }) =&gt; {
    const { site } = useStaticQuery(query)
  
    const {
      buildTime,
      siteMetadata: {
        siteUrl,
        defaultTitle,
        defaultDescription,
        defaultBanner,
        headline,
        siteLanguage,
        ogLanguage,
        author,
        twitter,
        facebook,
      },
    } = site
  
    const seo = {
      title: title || defaultTitle,
      description: desc || defaultDescription,
      image: `${siteUrl}${banner || defaultBanner}`,
      url: `${siteUrl}${pathname || &quot;/&quot;}`,
      date_published: published,
      date_modified: modified,
    }
  
    // Default Website Schema
    const schemaOrgJSONLD = [
      {
        &quot;@context&quot;: &quot;http://schema.org&quot;,
        &quot;@type&quot;: &quot;WebSite&quot;,
        url: siteUrl,
        name: defaultTitle,
        alternateName: headline ? headline : &quot;&quot;,
      },
    ]
  
    if (howto) {
      schemaOrgJSONLD.push({
        /* HowTo Schema here */
      })
    }
    
    if (faq) {
      schemaOrgJSONLD.push({
        /* FAQ Schema here */
      })
    }
  
    if (article) {
      schemaOrgJSONLD.push({
        /* Regular Article Schema */
        &quot;@context&quot;: &quot;http://schema.org&quot;,
        &quot;@type&quot;: &quot;Article&quot;,
        author: {
          &quot;@type&quot;: &quot;Person&quot;,
          name: author,
        },
        copyrightHolder: {
          &quot;@type&quot;: &quot;Person&quot;,
          name: author,
        },
        copyrightYear: &quot;2019&quot;,
        creator: {
          &quot;@type&quot;: &quot;Person&quot;,
          name: author,
        },
        publisher: {
          &quot;@type&quot;: &quot;Organization&quot;,
          name: author,
          logo: {
            &quot;@type&quot;: &quot;ImageObject&quot;,
            url: `${siteUrl}${defaultBanner}`,
          },
        },
        datePublished: seo.date_published,
        dateModified: seo.date_modified,
        description: seo.description,
        headline: seo.title,
        inLanguage: siteLanguage,
        url: seo.url,
        name: seo.title,
        image: {
          &quot;@type&quot;: &quot;ImageObject&quot;,
          url: seo.image,
        },
        mainEntityOfPage: seo.url,
      })
    }
  
    return (
      &lt;&gt;
        &lt;Helmet title={seo.title}&gt;
          &lt;html lang={siteLanguage} /&gt;
          &lt;meta name=&quot;description&quot; content={seo.description} /&gt;
          &lt;meta name=&quot;image&quot; content={seo.image} /&gt;
          {/* Schema.org tags */}
          &lt;script type=&quot;application/ld+json&quot;&gt;
            {JSON.stringify(schemaOrgJSONLD)}
          &lt;/script&gt;
        &lt;/Helmet&gt;
        &lt;Facebook
          desc={seo.description}
          image={seo.image}
          title={seo.title}
          type={article ? &quot;article&quot; : &quot;website&quot;}
          url={seo.url}
          locale={ogLanguage}
          name={facebook}
        /&gt;
        &lt;Twitter
          title={seo.title}
          image={seo.image}
          desc={seo.description}
          username={twitter}
        /&gt;
      &lt;/&gt;
    )
  }
  
  export default SEO
  
  SEO.propTypes = {
    title: PropTypes.string,
    desc: PropTypes.string,
    banner: PropTypes.string,
    pathname: PropTypes.string,
    published: PropTypes.string,
    modified: PropTypes.string,
    article: PropTypes.bool,
    webpage: PropTypes.bool,
    node: PropTypes.object,
  }
  
  SEO.defaultProps = {
    title: null,
    desc: null,
    banner: null,
    pathname: null,
    published: null,
    modified: null,
    article: false,
    webpage: false,
    node: null,
  }
  
  const query = graphql`
    query SEO {
      site {
        buildTime(formatString: &quot;YYYY-MM-DD&quot;)
        siteMetadata {
          siteUrl
          defaultTitle: title
          defaultDescription: description
          defaultBanner: logo
          headline
          siteLanguage
          ogLanguage
          author
          logo
          twitter
          facebook
        }
      }
    }
  `
  </code></pre>
  <h3>The problems I can see are:</h3>
  <ol>
  <li>How to test for what schema type to use and print it</li>
  <li>Include breadcrumbs schema for all types</li>
  <li>Print only a single schema JSON-LD script tag, avoiding any duplicate schema</li>
  <li>Is using frontmatter in markdown files suitable to store complex schema data</li>
  <li>Retrieving frontmatter data for schema</li>
  </ol>