Gatsby SEO Component

Date published: 24-Aug-2020
2 min read / 493 words
Author: Paul Scanlon

Gatsby
JavaScript

In this post i'm going to explain how to create an Seo component for use in a Gatsby application or site, I seem to always end up writing this over and over again so first and foremost this is for my reference but if you're interested read on.

  • Demo Site
  • Demo Repo

For the component to work we'll also need to install react-helment and a Gatsby plugin gatsby-plugin-react-helmet

Install the following dependencies to get started 👇

npm install react-helmet gatsby-plugin-react-helmet --save

Then open up your gatsby-config.js and add the plugin to the plugins array

// gatsby-config.js
module.exports = {
plugins: [`gatsby-plugin-react-helmet`],
}

While we're in gatsby-config.js have a look over what keys you have in you siteMetadata, this is a standard one I've used a few times and has most of what you'll need to pass on to the Seo component when we build it in a moment.


// gatsby-config.js
module.exports = {
+ siteMetadata: {
+ title: 'Mr Website',
+ description: 'A website about Mr Website',
+ keywords: ['React', 'Gatsby', 'JAMStack'],
+ url: 'https://gatsby-seo-component.netlify.app',
+ ogImage: 'images/og-image.jpg',
+ favicon: {
+ ico: 'images/favicon.ico',
+ sm: 'images/favicon-16x16.png',
+ lg: 'images/favicon-32x32.png'
+ },
+ lang: `en`
+ },
plugins: [`gatsby-plugin-react-helmet`],
}

I'd like to point one thing out here and that's the path to the .jpg and .ico, .png files. Those paths relate to an images directory that I've created in ./static. The static directory is where Gatsby looks to load statically served content. For the above to work your directory structure would probably look a bit like this 👇

src
|-- pages
|-- components
|-- utils
static
|-- images
gatsby-config.js
package.json

No we can build the component.

// src/components/seo.js
import React from "react"
import PropTypes from "prop-types"
import { Helmet } from "react-helmet"
export const Seo = ({
title,
description,
keywords,
url,
ogImage,
favicon,
lang,
type,
page,
path,
}) => {
return (
<Helmet>
<title>{`${title} | ${page}`}</title>
<html lang={lang} />
<meta name="description" content={description} />
<meta name="image" content={`${url}/${ogImage}`} />
<meta name="image:alt" content={description} />
<meta name="keywords" content={keywords.join(", ")} />
<link
rel="icon"
type="image/png"
sizes="16x16"
href={`${url}/${favicon.ico}`}
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href={`${url}/${favicon.sm}`}
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href={`${url}/${favicon.lg}`}
/>
{/* Facebook */}
<meta property="og:type" content={type} />
<meta property="og:title" content={page} />
<meta property="og:url" content={`${url}${path}`} />
<meta property="og:description" content={description} />
<meta property="og:image" content={`${url}/${ogImage}`} />
<meta property="og:image:alt" content={description}></meta>
{/* Twitter */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={page} />
<meta name="twitter:url" content={`${url}${path}`} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={`${url}/${ogImage}`} />
<meta name="twitter:image:alt" content={description}></meta>
</Helmet>
)
}
Seo.propTypes = {
/** The site title */
title: PropTypes.string.isRequired,
/** The site description */
description: PropTypes.string.isRequired,
/** Keywords to use in meta keywords */
keywords: PropTypes.arrayOf(PropTypes.string),
/** The site URL */
url: PropTypes.string.isRequired,
/** Image url to use for opengraph image */
ogImage: PropTypes.string,
/** Favicon image urls**/
favicon: PropTypes.shape({
ico: PropTypes.string,
sm: PropTypes.string,
lg: PropTypes.string,
}),
/** Lang to use as meta lang */
lang: PropTypes.string.isRequired,
/** The type of meta - useful for Facebook */
type: PropTypes.oneOf(["website", "article"]).isRequired,
/** The page name */
page: PropTypes.string.isRequired,
/** The path to the page */
path: PropTypes.string.isRequired,
}

You should be able to see from each of the prop descriptions what each one does and from title to lang you'll be using the values from siteMetadata... more on that in a moment.

type, page and path will most likely be unique to each page of your site so we won't pull this out of the siteMetadata instead I'll show you how to pass those in as props... more on that in moment too.

There's now two key steps to making this work you can do them in either order.

useSiteMetadata hook

This is a utility function that can be used in conjunction with useStaticQuery and graphql from "gatsby"

// src/utils/useSiteMetadata.js
import { useStaticQuery, graphql } from "gatsby"
export const useSiteMetadata = () => {
return useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
keywords
url
ogImage
favicon {
ico
sm
lg
}
lang
}
}
}
`
)
}

Add Seo component to pages

The simplest approach is to import the Seo component and the useSiteMetadata hook for each [page].js from src/pages. The better approach is to create a page layout component but I won't cover that in this post.

To implement the Seo component each [page].js might look something like this 👇

// src/pages/index.js
import React, { Fragment } from "react"
import { Link } from "gatsby"
import { Seo } from "../components/seo"
import { useSiteMetadata } from "../utils/useSiteMetadata"
const IndexPage = () => {
const {
site: {
siteMetadata: {
title,
description,
keywords,
url,
ogImage,
favicon,
lang,
},
},
} = useSiteMetadata()
return (
<Fragment>
<Seo
title={title}
description={description}
keywords={keywords}
url={url}
ogImage={ogImage}
favicon={favicon}
lang={lang}
type="website"
page="Home"
path=""
/>
<h1>This is index.js</h1>
...
</Fragment>
)
}
export default IndexPage

I've destructured all the props in the above code snippet but you can of course use JavaScript's native spread operator to achieve the same thing but in the interest of making this post as clear as possible I'll try and avoid draw the rest of the fucking owl

You should also see near the bottom you can see where i've added values for type, page and path

type = "website"
page = "Home"
path = ""

As always there's a million ways to do the same thing but I hope this gives you some indication of one way to do it but in most cases you'll likely need to make a change here and there.... and that's absolutely fine!

SEO Validation

There's a few ways to test if your meta data is working correctly, the following are two links I use to preview what my meta data will render as if I post a link to either Twitter or Facebook



If you've enjoyed this post I'd love to hear from you: @pauliescanlon