I have a basic blog setup with Gatsby and at the time of posting this question there lacks good documentation for SEO components. 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.
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 rich snippets which will vary depending on what the blog post type is. For example, I might have a regular post which would print Article schema, some posts might be How-to, in which case I’d like to print HowTo schema instead of Article. At some point I might write a post with would suit FAQ schema.
I don’t know if this is the best approach but here’s what I’m thinking:
1. In frontmatter set the schema type I want to true, leave the rest false.
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?
---
title: Hello World
description: How to say hello
article: false
how-to: true
faq: false
---
2. Test for true/false in the SEO component and print the correct schema.
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 gatsby advanced starter component and the gatsby starter prismic component but neither do quite what I need. Here’s mine:
import React from "react"
import Helmet from "react-helmet"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import Facebook from "./Facebook"
import Twitter from "./Twitter"
const SEO = ({
title,
desc,
banner,
pathname,
published,
modified,
article,
webpage,
node,
}) => {
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 || "/"}`,
date_published: published,
date_modified: modified,
}
// Default Website Schema
const schemaOrgJSONLD = [
{
"@context": "http://schema.org",
"@type": "WebSite",
url: siteUrl,
name: defaultTitle,
alternateName: headline ? headline : "",
},
]
if (howto) {
schemaOrgJSONLD.push({
/* HowTo Schema here */
})
}
if (faq) {
schemaOrgJSONLD.push({
/* FAQ Schema here */
})
}
if (article) {
schemaOrgJSONLD.push({
/* Regular Article Schema */
"@context": "http://schema.org",
"@type": "Article",
author: {
"@type": "Person",
name: author,
},
copyrightHolder: {
"@type": "Person",
name: author,
},
copyrightYear: "2019",
creator: {
"@type": "Person",
name: author,
},
publisher: {
"@type": "Organization",
name: author,
logo: {
"@type": "ImageObject",
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: {
"@type": "ImageObject",
url: seo.image,
},
mainEntityOfPage: seo.url,
})
}
return (
<>
<Helmet title={seo.title}>
<html lang={siteLanguage} />
<meta name="description" content={seo.description} />
<meta name="image" content={seo.image} />
{/* Schema.org tags */}
<script type="application/ld+json">
{JSON.stringify(schemaOrgJSONLD)}
</script>
</Helmet>
<Facebook
desc={seo.description}
image={seo.image}
title={seo.title}
type={article ? "article" : "website"}
url={seo.url}
locale={ogLanguage}
name={facebook}
/>
<Twitter
title={seo.title}
image={seo.image}
desc={seo.description}
username={twitter}
/>
</>
)
}
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: "YYYY-MM-DD")
siteMetadata {
siteUrl
defaultTitle: title
defaultDescription: description
defaultBanner: logo
headline
siteLanguage
ogLanguage
author
logo
twitter
facebook
}
}
}
`
The problems I can see are:
- How to test for what schema type to use and print it
- Include breadcrumbs schema for all types
- Print only a single schema JSON-LD script tag, avoiding any duplicate schema
- Is using frontmatter in markdown files suitable to store complex schema data
- Retrieving frontmatter data for schema
2
Answers
I settled on this solution.
In frontmatter:
Query for it with GraphQL like you would for your other data:
Pass it to your SEO component:
In your SEO component, you can use it like this (do the same for all your types). You can setup your Posts and SEO component for as my types as you need, FAQ, Course etc:
Finally, in React Helmet we have:
Just found great article on the topic: https://www.iamtimsmith.com/blog/creating-a-better-seo-component-for-gatsby/
Helped me to create rich snippets dynamically for all pages in my app.
Main idea: pass
children
in youseo.js
:and then on any page/component: