Building SEO-Powered Websites With Gatsby
Tags: seo, react, gatsby
Published: 2021-01-04 16:30:56

Gatsby is a framework that leverages React for building SEO-powered websites. Many websites are created to be discovered, hence, SEO support is an important factor in such websites.

Many factors influence SEO such as [accessibility](https://webaim.org/blog/web-accessibility-and-seo/), correct meta-information (at the head tag) and some other external tools. Gatsby supports using appropriate meta information for individual pages to improve their presence online.

In this article, we'll look at the limitations of [Create React App](https://reactjs.org/docs/create-a-new-react-app.html) in respect to SEO, and how Gatsby solves this with SSR. Furthermore in the article, we'll go through a tutorial on building a basic website with good SEO.

## Why CRA is not a good tool for SEO

CRA is an opinionated tool used for building React applications but cannot be used for SEO. Here's why:

When using React, you'll most probably be using a library like [`react-helmet`](https://www.npmjs.com/package/react-helmet) (a document head manager for React) for updating meta-information about the site. The limitations of such libraries is that they contain JavaScript, which means they can only be executed on the browser (so JavaScript can run).

SEO crawlers or social media tools that inspect head tags of websites (to display a card, perhaps) would not execute that JavaScript. Instead, they make use of the default meta information they can find. In CRA, the default title in `public/index.html` is "React App". This means, for every page you create (based on routes), they will all have the same title. They only show different titles when they are executed on the client's browser because the `react-helmet` library gets the chance to be executed, thereby updating the head tags. [This article](https://dillionmegida.com/p/how-to-configure-meta-tags-in-react-app-dynamically/#react-helmet-is-not-the-perfect-solution) contains more information.

## How Gatsby solves React SEO problems with SSR

Gatsby is a Static Site Generator (SSG) which uses Server Side Rendering (SSR) to generate static pages during the build process.

What this means is that you provide dynamic meta information in every page, and during the process of static site generation, pages are server-side rendered with the specified meta information, thereby making static pages with their own details.

With this technique, every page contains its own meta title, meta description and basically every meta information.

The following tutorial shows how Gatsby improves SEO in web applications.

## Building an SEO powered site with Gatsby

We'll be building a basic website with two pages: `/` - Home and `/about` - About Us. These two pages would have their own meta information attached to them during the build process.

To get started, let's created our Gatsby project. Run the following in your terminal:

```shell
gatsby new new-project https://github.com/gatsbyjs/gatsby-starter-default
```

This pulls the default template, and installs all necessary dependencies. In the `src` directory, you'll find three directories: `components`, `images` and `pages`.

As you may observe, the template already comes with some configurations for seo and optimizing images. To build our project afresh, delete the following files/directories:

```shell
components/header.js
components/layout.css
components/layout.js
components/image.js
pages/404.js
pages/page-2.js
pages/index.js
pages/using-typescript.tsx
```

This will leave us with `components/seo.js` and `images`.

In a future series, we'll explore the [`gatsby-image`](https://www.gatsbyjs.com/plugins/gatsby-image/) plugin used in `components/images.js`. But for now, understand that it performs optimizations on images.

Let's briefly explore the content of `components/seo.js`

```js
function SEO({ description, lang, meta, title }) {
	const { site } = useStaticQuery(
		graphql`
			query {
				site {
					siteMetadata {
						title
						description
						author
					}
				}
			}
		`
	);

	const metaDescription = description || site.siteMetadata.description;
	const defaultTitle = site.siteMetadata?.title;

	return (
		<Helmet
			htmlAttributes={{
				lang,
			}}
			title={title}
			titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
			meta={[
				{
					name: `description`,
					content: metaDescription,
				},
				{
					property: `og:title`,
					content: title,
				},
				{
					property: `og:description`,
					content: metaDescription,
				},
				{
					property: `og:type`,
					content: `website`,
				},
				{
					name: `twitter:card`,
					content: `summary`,
				},
				{
					name: `twitter:creator`,
					content: site.siteMetadata?.author || ``,
				},
				{
					name: `twitter:title`,
					content: title,
				},
				{
					name: `twitter:description`,
					content: metaDescription,
				},
			].concat(meta)}
		/>
	);
}

SEO.defaultProps = {
	lang: `en`,
	meta: [],
	description: ``,
};

SEO.propTypes = {
	description: PropTypes.string,
	lang: PropTypes.string,
	meta: PropTypes.arrayOf(PropTypes.object),
	title: PropTypes.string.isRequired,
};
```

_Note that_ this component can look a bit different in another template, or you may do it differently.

The `SEO` component receives four props: `title`, `description`, `lang` and `meta` with `title` as required. You can specify more props if you want or take out those you don't need.

This allows different pages to specify their titles, descriptions and other meta information specific to them. 

`Helmet` is from `react-helmet` but is used a bit different from how it is used in CRA. It works with [`gatsby-plugin-react-helmet`](https://www.gatsbyjs.com/plugins/gatsby-plugin-react-helmet/) which provides server rendering support.

`components/seo.js` also contains some GraphQL which we will cover in a future series.

The `Helmet` plugin during the build process populates all pages with their respective meta information depending on the inputs provided during development.

Now let's add our pages.

With Gatsby, you do not need any routing packages for determining components to show based on specific URLs. To create a page, all you need to do is add the component's file directly under the `pages` directory.

To create the two pages for our project, add two files: `index.js` for `/` and `about.js` for `/about`.

Before proceeding with our pages, let's add a layout.

Create `components/layout.js` and `components/header.js`.

Add the following in `components/header.js`:

```js
import { Link } from "gatsby";
import React from "react";
import PropTypes from "prop-types";

const Header = ({ siteName }) => {
	return (
		<header className="header">
			<div className="site">
				<Link to="/">{siteName}</Link>
			</div>
			<nav>
				<ul>
					<li>
						<Link to="/">Home</Link>{" "}
					</li>
					<li>
						<Link to="/about">About</Link>{" "}
					</li>
				</ul>
			</nav>
		</header>
	);
};

Header.propTypes = {
	siteName: PropTypes.string.isRequired,
};

export default Header;
```

Same React. The only thing new here is a different `Link` component from Gatsby is used.

In the `components/layout.js`, add the following:

```js
import React from "react";
import Header from "./header";

const Layout = ({ children }) => {
	return (
		<div>
			<Header siteName="My Project" />
			{children}
		</div>
	);
};

export default Layout;
```

For the pages, add the following to `index.js`:

```js
import React from "react";
import { Link } from "gatsby";

import Layout from "../components/layout";
import SEO from "../components/seo";

const IndexPage = () => (
	<Layout>
		<SEO title="Homepage" description="This is the homepage of my website" />
		<div className="hero-container">
			<div className="hero-left">
				<span className="hero-left-heading">Buy your Laptops Here</span>
				<span className="hero-left-text">
					You can get quality laptops here. What are you waiting for to make
					your first purchase?
				</span>
				<Link className="hero-left-link" to="/about">
					Learn More about me
				</Link>
			</div>
			<div className="hero-right">
				<div className="hero-right-image-container">
					<img src={require("../images/laptop.jpg")} />
				</div>
			</div>
		</div>
	</Layout>
);

export default IndexPage;
```

I added an unsplash image to `images` and required it `require('../images/laptop.jpg')` as seen above.

We'll look at the usage of the `SEO` component soon.

For `pages/about.js`, add the following:

```js
import React from "react";

import Layout from "../components/layout";
import SEO from "../components/seo";

const AboutPage = () => (
	<Layout>
		<SEO
			title="About my website"
			description="This page contains more information about my website and the work that I do"
		/>
		<main>
			<div className="about-container">
				<h1>About Me</h1>
				<p>I sell quality computers!! Okay?</p>
			</div>
		</main>
	</Layout>
);

export default AboutPage;
```

Create a new directory called `styles` under `src` and create a new file: `global.css`. Copy the following css styles to that file:

```css
* {
	box-sizing: border-box;
}

body {
	margin: 0;
	padding: 0;
}

ul {
	padding: 0;
	list-style: none;
}

.header {
	padding: 20px;
	background-color: rgb(5, 42, 123);
	display: flex;
	justify-content: space-between;
	align-items: center;
}

.header .site a {
	color: white;
	font-size: 30px;
	text-decoration: none;
}

.header nav ul {
	display: flex;
}
.header nav ul li a {
	color: white;
	margin: 20px;
}

.hero-container {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 100px 30px;
}

.hero-left {
	display: flex;
	flex-direction: column;
	max-width: 500px;
	padding: 20px;
}

.hero-left-heading {
	font-size: 40px;
	color: rgb(181, 0, 154);
	font-weight: bold;
}

.hero-left-text {
	margin: 20px 0;
}

.hero-left-link {
	display: block;
	width: max-content;
	padding: 15px;
	border-radius: 5px;
	background-color: rgb(181, 0, 154);
	color: white;
	display: flex;
	justify-content: center;
	align-items: center;
	text-decoration: none;
}

.hero-right-image-container {
	width: 600px;
	height: 400px;
	overflow: hidden;
}
.hero-right-image-container img {
	object-fit: cover;
	width: 100%;
	height: 100%;
}

.about-container {
	margin-top: 100px;
	text-align: center;
}

.about-container h1 {
	font-size: 50px;
}
```

For the global stylesheet to be used for the whole site, the `gatsby-browser.js` API file would be used.

[`gatsby-browser.js`](https://www.gatsbyjs.com/docs/api-files-gatsby-browser/) is a reserved API file that gives access to actions within the browser.

In `gatsby-browser.js` (at the root of your project), add the following:

```js
import "./src/styles/global.css";
```

When you run the gatsby server for your project (`gatsby develop`), you'll get the following on `localhost:8000`:

For `/`:

![Gatsby Basic Website Homepage](//images.ctfassets.net/zojzzdop0fzx/7875n7hVikd7FcZqzEeJpy/e417a0b3959f3165f94c48477b55164d/Screenshot_2020-12-04_at_20.31.14.png)

For `/about`:

![Gatsby Basic Website About page](//images.ctfassets.net/zojzzdop0fzx/7msBNe2DZ5Yc5q4c0oDWjB/5b2f5268c307518f7ae0f3712f07c7aa/Screenshot_2020-12-04_at_20.31.46.png)

The `SEO` component makes all the pages unique and SEO-ready. For `index.js`, we have:

```js
<SEO title="Homepage" description="This is the homepage of my website" />
```

Just as we have configured the `SEO` component using `react-helmet`, this updates the meta information for the homepage during the build process. This way, the first thing crawlers will see is the unique meta details for the page, as they do not require any JavaScript to be updated.

To test this, do the following:

- build for project for production (`gatsby run build`)
- serve the production build (`gatsby run serve`)

This will run the built content on `localhost:9000`.

You can use `curl` on your terminal to inspect the source code (or run inspect on the browser).

```shell
curl localhost:9000
```

The result:

![Gatsby Basic Website Homepage Source](//images.ctfassets.net/zojzzdop0fzx/76dXqrFRdWg5MDbPbHK0vM/9e8921df7864fc997bd97ad8e4cfeb5b/Screenshot_2020-12-04_at_21.07.37.png)

The reason it came out as "Homepage | Gatsby Default Starter" is because of the prop `titleTemplate` provided by `Helmet` which was configured like so in the `SEO` template:

```js
titleTemplate={defaultTitle ? `%s | ${defaultTitle}` : null}
```

This appends a default title to every title provided by the pages.

## Conclusion

In this article, we looked at how Gatsby solves the problem of SEO using server side rendering for generating static pages.

The basic example used in the tutorial shows how each page contain their own meta information that can easily be crawled by SEO bots or social media tools. 

*This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](https://www.thisdotlabs.com).*

*This Dot Media is focused on creating an inclusive and educational web for all.  We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co).*