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:
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
---
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
}
}
}
`
In frontmatter:
---
type: howto // Use either 'article' or 'howto'
---
Query for it with GraphQL like you would for your other data:
frontmatter {
title
published(formatString: "MMMM DD, YYYY")
modified(formatString: "MMMM DD, YYYY")
description
type
}
Pass it to your SEO component:
<SEO
title={post.frontmatter.title}
desc={post.frontmatter.description}
published={post.frontmatter.published}
modified={post.frontmatter.modified}
type={post.frontmatter.type}
/>
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:
const schemaType = type
if (schemaType === "howto") {
schemaHowTo = {
// Your howto schema here
}
}
if (schemaType === "article") {
schemaArticle = {
// Your article schema here
}
}
Finally, in React Helmet we have:
<Helmet>
{schemaType === "howto" && (
<script type="application/ld+json">
{JSON.stringify(schemaHowTo)}
</script>
)}
{schemaType === "article" && (
<script type="application/ld+json">
{JSON.stringify(schemaArticle)}
</script>
)}
...
<Helmet>
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 you seo.js
:
return (
<Helmet
htmlAttributes={{lang: `en`}}
titleTemplate={`%s | ${data.site.siteMetadata.title}`}
>
<title>{title}</title>
{children}
</Helmet>
);
and then on any page/component:
return (
<SEO title={title} description={description} image={image} slug={slug}>
<script type='application/ld+json'>
{`{
'@context': 'https://schema.org',
'@type': 'LiveBlogPosting',
'@id': 'https://example.com',
'headline': ${title},
'description': ${description}
}`}
</script>
</SEO>
);
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With