Image: Oleksandr Pidvalnyi
Setting up a SEO React component for your Gatsby site
Tags: seo, web dev, search, gatsby
April 03, 2023
SEO & Gatsby
Handling SEO (Search Engine Optimization) in a Gatsby 5 project can be overwhelming at first. There are many aspects to cover. In this post, I'll take a look at an example implementation of an SEO component that sets you up for social media link sharing and other meta tags.
Before building an SEO component in Gatsby 5, it's important to understand why metadata such as title and description tags are important for helping search engines understand your content and decide when to surface it in search results. Gatsby 5 includes the Head API, which offers native support for SEO.
Why you should care about metatags
Metatags provide information about your site to search engines and social media platforms. This information helps them understand your content and decide when to surface it in search results or when someone shares your website.
What Gatsby gives you for SEO metatags
Gatsby provides drop-in support for server rendering of metadata, which helps your site perform better in search engines. You can use the Gatsby Head API to change the document head of your pages. It's also good practice to store site metadata in gatsby-config.js
and create an SEO component that leverages the metadata for inclusion in site pages at build time.
You can learn more about adding an SEO component to a Gatsby project by reading the official documentation or this tutorial on DigitalOcean.
The SEO component
Let's take a look at how it all fits together.
The component returns a set of meta tags that provide information about the page to search engines and social media platforms.
import React from "react";
import { useStaticQuery, graphql } from "gatsby";
import { getImage, getSrc } from "gatsby-plugin-image";
const Seo = ({ excerpt, title, slug, hero_image, location, params, pageContext }) => {
const data = useStaticQuery(graphql`
query SiteMetadata {
site {
siteMetadata {
title
description
image
siteUrl
}
}
}
`);
const { title: siteTitle, description: siteDescription, image: siteImage, siteUrl } = data.site.siteMetadata;
const blurb = excerpt || siteDescription;
const heroImageObject = getImage(hero_image || null);
const heroImageUrl = getSrc(heroImageObject);
const fullTitle = `${title.replace("/", "")} ⍟ ${siteTitle}`;
const fullImageUrl = heroImageUrl ? `${siteUrl}${heroImageUrl}` : `${siteUrl}${siteImage}`;
return <>
{/* Remove any <html> tag from here else you'll face SSR issues due to mismatch server/client*/}
{/* title of page */}
<title>{fullTitle}</title>
<meta property="og:title" content={fullTitle} />
{/* type of page */}
<meta property="og:type" content={slug ? "article" : "website"} />
{/* canonical URL of page */}
<meta property="og:url" content={siteUrl + location.pathname} />
{/* description of page */}
<meta name="description" content={blurb} />
<meta property="og:description" content={blurb} />
{/* image of page */}
<meta property="og:image" content={fullImageUrl} />
{/* name of website */}
<meta property="og:site_name" content={siteTitle} />
</>;
};
export default Seo;
How it works:
The component takes in excerpt
, title
, slug
, and hero_image
as props but also has fallbacks for when the props aren't supplied. Generally, this component treats actual posts as 'article' type and any other paths are considered as 'website' type and use the site metadata instead of page-specific data.
The component uses the useStaticQuery
hook to query the site's metadata, which includes the site's title, description, image, and URL. This data is then destructured into their respective variables.
The component also defines several variables:
blurb
The page's description. If an excerpt is provided as a prop, it uses that. Otherwise, it uses the site's description from the metadata.
heroImage
The hero image of the page. If a hero image is provided as a prop, it uses that. Otherwise, it is set to null.
heroImageObject
An object representing the hero image using Gatsby's getImage
function.
heroImageUrl
The URL of the hero image using Gatsby's getSrc
function.
fullTitle
The full title of the page. It concatenates the page title (with any forward slashes removed) with the site title, separated by a "⍟" symbol.
fullImageUrl
The full URL of the hero image. If a hero image URL is available, it concatenates the site URL with the hero image URL. Otherwise, it concatenates the site URL with the site image from the metadata.
The component returns a fragment containing several meta tags and a title tag for SEO purposes:
lang="en"
We could set the language of the page to English in this example:
<html lang="en" />
However, this lead to SSR issues due to mismatch between server/client. So, I recommend using the gatsby-ssr.js
to set the language of the page to English:
// in gatsby-ssr.js
exports.onRenderBody = ({ setHtmlAttributes }) => {
setHtmlAttributes({ lang: 'en' });
};
title
This specifies the title of the page as it should appear in search engine results and in the browser tab. It's one of the most important factors in helping search engines understand what the page is about and can improve the page's ranking for relevant keywords.
I've set the title of the page to the value of fullTitle
.
<title>{fullTitle}</title>
og:title
This specifies the title of the page as it should appear when shared on social media platforms like Facebook and Twitter. It's often displayed as the main title of the shared content.
We set the Open Graph title of the page to the value of fullTitle
.
<meta property="og:title" content={fullTitle} />
name="description"
This provides a brief description of the page content. It's often displayed in search engine results as a snippet below the page title and can help users decide if the page is relevant to their search.
In this example, we set the page's description to the value of blurb
.
<meta name="description" content={blurb} />
og:description
This provides a brief description of the page content as it should appear when shared on social media platforms. It's often displayed as a snippet below the title of the shared content.
I've set the Open Graph description of the page to the value of blurb
.
<meta property="og:description" content={blurb} />
og:type
This specifies the type of content on the page (e.g., “article” or “website”). It helps social media platforms understand how to display the shared content.
Set the Open Graph type of the page to "article" if a slug is provided as a prop; otherwise, it sets it to "website".
<meta property="og:type" content={slug ? "article" : "website"} />
og:url
This specifies the canonical URL of the page. It helps social media platforms understand where to direct users when they click on the shared content.
Set the Open Graph URL of the page to the value of location.pathname
.
<meta property="og:url" content={location.pathname} />
og:image
This specifies an image to be displayed when the page is shared on social media platforms. It helps make the shared content more visually appealing and can improve engagement.
Here, I set the Open Graph image of the page to the value of fullImageUrl
.
<meta property="og:image" content={fullImageUrl} />
og:site_name
This specifies the name of the website as it should appear when the page is shared on social media platforms. It helps users understand where the shared content is coming from.
I use siteTitle
to set the Open Graph site name.
<meta property="og:site_name" content={siteTitle} />
Using the component
Here's an example of how you can use the SEO component within a template page file for an MDX post (ie. {mdx.frontmatter__slug}.js
) with a GraphQL query to get the frontmatter data:
import Seo from "../components/Seo";
export const query = graphql`
query($slug: String!) {
mdx(frontmatter: { slug: { eq: $slug } }) {
frontmatter {
excerpt
title
slug
hero_image {
childImageSharp {
gatsbyImageData
}
}
}
}
}
`;
const PostTemplate = ({ data }) => {
const { excerpt, title, slug, hero_image } = data.mdx.frontmatter;
return (
<>
{/* Page content would be in here */}
{/*
The SEO component could go inside the component like below
commented code. However, this will lead to nested meta tags
in your html. Use Head export to have Gatsby edit the actual
document head
*/}
{/*<Seo excerpt={excerpt} title={title} slug={slug} hero_image={hero_image} />*/}
</>
);
};
// Export the named Head component which Gatsby
// will use to render the head section of the page
export const Head = ({ location, data }) =>
<Seo
location={location}
title={data.mdx.frontmatter.title}
hero_image={data.mdx.frontmatter.hero_image}
excerpt={data.mdx.excerpt}
/>;
export default PostTemplate;
You can still use this component when the page in question is not an MDX file. Here's an example of how you can use the Seo component with only the title prop set:
import Seo from "./Seo";
const Page = () => {
return (
<>
{/* the page's content would be here in the component*/}
</>
);
};
// In this case, we only need to set the title prop and pass in the location (the current page's location)
export const Head = ({ location }) => <Seo title="Home Page" location={location} />;
export default Page;
How to test
You can also view the source code of your webpage and look for the tags in the head section, or use web developer tools built into the browser. You just want to make sure you can see all the tags you expect to see.
To test if your SEO metatags are working as expected, you can use online tools like Meta Tags, Meta Tag Analyzer, or Meta Tags Checker to preview how your webpage will look on different platforms.
Conclusion
I hope this helps you get all the necessary data in place for all your pages and posts.
By including a component to set these meta tags in your pages, you can provide search engines and social media platforms with important information about your content, which can help improve your site's visibility and engagement.