Adding open graph and other SEO tags with current URL in gatsby site

Published on August 11, 2021 • 2m read

How to improve SEO and social presence by adding general meta tags and open graph meta tags like og:image, og:url etc with current URL in a gatsbyjs website.

How to add SEO and open graph url meta to a gatsby site

Image by Werner Moser from Pixabay.

Managing blog and documentation with Gatsby is one of many perk the open source frontend framework provides. In this tutorial we will explore how we've added open graph and other search engine optimization related head tags in our website.

Pre-requisites for Gatsby SEO tags

I will try to be as abstract as possible. There are bunch of ways to render pages in gatsby and I will not cover them here. Instead we will focus on creating react components which will be responsible for plugging our SEO tags to pages.

We will need the following packages first.

  • gatsby-plugin-react-helmet - For adding things to the head of our website.
  • gatsby-plugin-image - For getting images to put inside og:image tags.

Let's install them.

[bash]
1npm install gatsby-plugin-react-helmet \
2 react-helmet \
3 gatsby-plugin-image \
4 gatsby-plugin-sharp \
5 gatsby-source-filesystem \
6 gatsby-transformer-sharp

Copied!

The plugin gatsby-transformer-sharp is needed because we will be using a fallback image for og:image tag in our SEO component.

Preparing SEO strategy

Let's assume we have images like this our directory.

[plain]
1my-website/
2├── src/
3│ ├── images/
4│ │ └── featured-image.jpg
5│ └── components/
6│ └── Seo.tsx
7└── gatsby-config.js

Copied!

Our goal is to make a SEO component that will:

  1. Render basic SEO tags like title, meta:description etc.
  2. Render all needed open graph tags like og:image, og:type, og:description, og:title etc.
  3. Add twitter specific tags.
  4. Must be extensible, i.e, should accept props so that it can be used on other page templates with sane defaults.
  5. Finally if no image is passed to the component, it will use src/images/featured-image.jpg for rendering a default featured image.

Make sure your open graph image is at-least 1200px wide and has a ratio of 1.91/1. So the minimum dimension should be 1200px X 627px.

We are going to need a few things in our gatsby config.

  1. Base website URL, because gatsby doesn't care what domain your website lives, we will need to be specific and tell it through the config file.
  2. Default value of title, description and other og tags would live in our config file for easier access.
  3. Setup the plugins we need to dynamically generate images.

For the rest of the tutorial, I will be writing TypeScript code. The types come automatically from the gatsby-plugin-typegen plugin.

Adding data and plugin to gatsby-config.js file

Let's add the following data to our gatsby-config.js file.

[js]gatsby-config.js
1module.exports = {
2 siteMetadata: {
3 // add general title and description
4 title: 'WPEForm No-Code Drag and Drop WordPress Form Builder',
5 description:
6 'Fast and modern WordPress no-code form builder for payments, quotation, quizzes, conversations & feedback.',
7 // site URL, no trailing slash
8 siteUrl: 'https://www.wpeform.io',
9 // other default open graph data
10 og: {
11 siteName: 'WPEForm - WordPress Form Builder',
12 twitterCreator: '@swashata',
13 },
14 },
15 plugins: [
16 'gatsby-plugin-image',
17 'gatsby-plugin-react-helmet',
18 {
19 resolve: 'gatsby-plugin-sharp',
20 options: {
21 defaults: {
22 quality: 70,
23 formats: ['auto', 'webp', 'avif'],
24 placeholder: 'tracedSVG',
25 },
26 },
27 },
28 `gatsby-remark-images`,
29 'gatsby-transformer-sharp',
30 {
31 resolve: 'gatsby-source-filesystem',
32 options: {
33 name: 'images',
34 path: './src/images/',
35 },
36 __key: 'images',
37 },
38 {
39 resolve: 'gatsby-source-filesystem',
40 options: {
41 name: 'pages',
42 path: './src/pages/',
43 },
44 __key: 'pages',
45 },
46 ],
47};

Copied!

The things inside siteMetadata is what we will query later in our SEO component. The rest of the plugins are there to make sure we get our image transformed by gatsby.

Preparing the SEO component

Now let's create a file src/components/Seo.tsx and have the following code.

[tsx]
1import { useStaticQuery, graphql } from 'gatsby';
2import * as React from 'react';
3import { Helmet } from 'react-helmet';
4import { useLocation } from '@reach/router';
5
6type ImageDataType = {
7 images: {
8 fallback: {
9 src: string;
10 };
11 };
12 width: number;
13 height: number;
14};
15
16export type SeoProps = {
17 /**
18 * Meta description. If not given, the one from the gatsby-config will be used.
19 */
20 description?: string;
21 /**
22 * Page title. If not given, the one from the gatsby-config will be used.
23 */
24 title?: string;
25 /**
26 * Meta keywords, will only be used if given.
27 */
28 keywords?: string;
29 /**
30 * Featured image, the data type should match the return type of `gatsbyImageData`.
31 * If not given, the default image will be used.
32 */
33 featuredImage?: ImageDataType;
34};
35export default function Seo(props: SeoProps) {
36 // first get our default data from gatsby config and default featured image
37 const { site, featuredImage } =
38 useStaticQuery<GatsbyTypes.SeoMetaDataQuery>(graphql`
39 query SeoMetaData {
40 site {
41 siteMetadata {
42 title
43 description
44 siteUrl
45 og {
46 siteName
47 twitterCreator
48 }
49 }
50 }
51 featuredImage: file(
52 absolutePath: { glob: "**/src/images/featured-image.png" }
53 ) {
54 childImageSharp {
55 gatsbyImageData(layout: FIXED, width: 1200)
56 }
57 }
58 }
59 `);
60
61 // determine the featured image from props
62 const ogImage =
63 props.featuredImage ??
64 (featuredImage?.childImageSharp
65 ?.gatsbyImageData as unknown as ImageDataType);
66
67 // determine title and description
68 const title = props.title ?? site?.siteMetadata?.title;
69 const description = props.description ?? site?.siteMetadata?.description;
70
71 // Use the location hook to get current page URL
72 const location = useLocation();
73
74 // construct the meta array for passing into react helmet.
75 const metas = [
76 // basic seo
77 {
78 name: 'description',
79 content: description,
80 },
81 {
82 name: 'og:image',
83 content: ogImage.images.fallback.src,
84 },
85 {
86 name: 'og:image:width',
87 content: `${ogImage.width}`,
88 },
89 {
90 name: 'og:image:height',
91 content: `${ogImage.height}`,
92 },
93 {
94 name: 'og:type',
95 content: 'website',
96 },
97 {
98 name: 'og:title',
99 content: title,
100 },
101 {
102 name: 'og:description',
103 content: description,
104 },
105 {
106 name: 'og:site_name',
107 content: site!.siteMetadata!.og!.siteName,
108 },
109 {
110 name: 'og:url',
111 content: `${site?.siteMetadata?.siteUrl}${location.pathname}`,
112 },
113 {
114 name: 'twitter:card',
115 content: 'summary_large_image',
116 },
117 {
118 name: 'twitter:description',
119 content: description,
120 },
121 {
122 name: 'twitter:title',
123 content: title,
124 },
125 {
126 name: 'twitter:image',
127 content: ogImage.images.fallback.src,
128 },
129 {
130 name: 'twitter:creator',
131 content: site!.siteMetadata!.og!.twitterCreator,
132 },
133 ];
134
135 // If we have keywords, then add it
136 if (props.keywords) {
137 metas.push({
138 name: 'keywords',
139 content: props.keywords,
140 });
141 }
142
143 return (
144 <Helmet>
145 <html lang="en" />
146 <meta charSet="utf-8" />
147 <title>{title}</title>
148 {metas.map(meta => (
149 <meta key={meta.name} name={meta.name} content={meta.content} />
150 ))}
151 </Helmet>
152 );
153}

Copied!

A few things to note in the component are:

  • The current path comes from the @reach/router which gatsby uses under the hood.
  • We use useStaticQuery for getting data from gatsby-config.js. The plugin gatsby-plugin-typegen automatically generates the type of the data.
  • We combine ${site.siteMetadata.siteUrl}${ogImage.images.fallback.src} to print http URLs for the open graph images.
  • In the static query, we have a file query with filter absolutePath: { glob: "**/src/images/featured-image.png" } to get the fallback featured image. This image will be used if the component is rendered without a featuredImage prop. If your image lives elsewhere, be sure to change the glob match pattern.

If you're not using typescript, the JavaScript version of the code can be found below.

[jsx]
1import { useStaticQuery, graphql } from 'gatsby';
2import * as React from 'react';
3import { Helmet } from 'react-helmet';
4import { useLocation } from '@reach/router';
5export default function Seo(props) {
6 // first get our default data from gatsby config and default featured image
7 const { site, featuredImage } = useStaticQuery(graphql`
8 query SeoMetaData {
9 site {
10 siteMetadata {
11 title
12 description
13 siteUrl
14 og {
15 siteName
16 twitterCreator
17 }
18 }
19 }
20 featuredImage: file(
21 absolutePath: { glob: "**/src/images/featured-image.png" }
22 ) {
23 childImageSharp {
24 gatsbyImageData(layout: FIXED, width: 1200)
25 }
26 }
27 }
28 `);
29 // determine the featured image from props
30 const ogImage =
31 props.featuredImage ?? featuredImage?.childImageSharp?.gatsbyImageData;
32 // determine title and description
33 const title = props.title ?? site?.siteMetadata?.title;
34 const description = props.description ?? site?.siteMetadata?.description;
35 // Use the location hook to get current page URL
36 const location = useLocation();
37 // construct the meta array for passing into react helmet.
38 const metas = [
39 // basic seo
40 {
41 name: 'description',
42 content: description,
43 },
44 {
45 name: 'og:image',
46 content: ogImage.images.fallback.src,
47 },
48 {
49 name: 'og:image:width',
50 content: `${ogImage.width}`,
51 },
52 {
53 name: 'og:image:height',
54 content: `${ogImage.height}`,
55 },
56 {
57 name: 'og:type',
58 content: 'website',
59 },
60 {
61 name: 'og:title',
62 content: title,
63 },
64 {
65 name: 'og:description',
66 content: description,
67 },
68 {
69 name: 'og:site_name',
70 content: site.siteMetadata.og.siteName,
71 },
72 {
73 name: 'og:url',
74 content: `${site?.siteMetadata?.siteUrl}${location.pathname}`,
75 },
76 {
77 name: 'twitter:card',
78 content: 'summary_large_image',
79 },
80 {
81 name: 'twitter:description',
82 content: description,
83 },
84 {
85 name: 'twitter:title',
86 content: title,
87 },
88 {
89 name: 'twitter:image',
90 content: ogImage.images.fallback.src,
91 },
92 {
93 name: 'twitter:creator',
94 content: site.siteMetadata.og.twitterCreator,
95 },
96 ];
97 // If we have keywords, then add it
98 if (props.keywords) {
99 metas.push({
100 name: 'keywords',
101 content: props.keywords,
102 });
103 }
104 return (
105 <Helmet>
106 <html lang="en" />
107 <meta charSet="utf-8" />
108 <title>{title}</title>
109 {metas.map(meta => (
110 <meta key={meta.name} name={meta.name} content={meta.content} />
111 ))}
112 </Helmet>
113 );
114}

Copied!

Using the SEO component in your pages

Let's say we have called pages/about.tsx. There we use the newly created component like this.

[tsx]
1import * as React from 'react';
2import { graphql, PageProps } from 'gatsby';
3import Seo from '../components/Seo';
4
5export default function Features(
6 props: PageProps<GatsbyTypes.AboutPagesDataQuery>
7) {
8 const { data } = props;
9 return (
10 <>
11 <Seo
12 title="About page title"
13 description="About page description"
14 featuredImage={data.aboutPageFeatured.childImageSharp.gatsbyImageData}
15 />
16 {/** Other page stuff goes here */}
17 </>
18 );
19}
20
21export const query = graphql`
22 query AboutPagesData {
23 aboutPageFeatured: file(
24 absolutePath: { glob: "**/src/images/about-us-image.png" }
25 ) {
26 childImageSharp {
27 gatsbyImageData(layout: FIXED, width: 1200)
28 }
29 }
30 }
31`;

Copied!

In the page query, we specifically query for the file src/images/about-us-image.png and pass the data to our SEO component for rendering a different featured image.

If you are using markdown frontmatter data, then you'd need to pass the image from frontmatter instead. The exact implementation will differ, but the concept remains the same.

Bonus: Adding canonical URLs to all your gatsby pages

We can use a plugin gatsby-plugin-canonical-urls for that. Simply install the plugin

[bash]
1npm install gatsby-plugin-canonical-urls

Copied!

And add it to your gatsby-config.js file.

[js]
1module.exports = {
2 // ... previous things
3 plugins: [
4 {
5 resolve: `gatsby-plugin-canonical-urls`,
6 options: {
7 siteUrl: `https://www.wpeform.io`,
8 },
9 },
10 // ... other plugins
11 ],
12};

Copied!

Now check the output of your pages, it should have something like this

[html]
1<link
2 rel="canonical"
3 href="https://www.wpeform.io/"
4 data-baseprotocol="https:"
5 data-basehost="www.wpeform.io"
6/>

Copied!


So we've now created a handy SEO component which can be used anywhere within your gatsby site for instant SEO tags. If you've liked this tutorial, please follow me on twitter and give a shoutout.

Start building beautiful forms!

Take the next step and get started with WPEForm today. You have the option to start with the free version, or get started with a trial. All your purchases are covered under 30 days Money Back Guarantee.