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, 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 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
(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 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:
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:
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
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
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
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
:
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:
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
:
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:
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:
* {
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
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:
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 /
:
For /about
:
The SEO
component makes all the pages unique and SEO-ready. For index.js
, we have:
<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).
curl localhost:9000
The result:
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:
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.