What I learned from my first real Gatsby site

July 27, 2022
9 min read

I've been doing React sites for several years and other frameworks for decades prior to that.  I have wanted to find something that is powered by JavaScript for speed but is also SEO friendly.  I have used a bunch of large, heavy frameworks in the past, but finding Gatsby was a breath of fresh air.  It's React-based and has a large and growing following.  It creates static sites from data sourced almost anywhere.  But because it is based on React, it's not truly static.  You can have all the bells and whistles you want due to JavaScript, json, REST and the modern web.

PLANNING AND DATA

In order to have a site, you need data.  Gatsby is great because you can source your data from almost any source.  Headless CMS systems are getting to be quite powerful and this is what I chose, mostly because I need something that scales and can handle any type of data without much work.

They support integrations with a multitude of headless CMS systems but since I have my own, I opted to use that.  The best way to handle custom integrations from what I found was to create a custom Source Plugin.

A Source Plugin is essentially a plugin that bridges the gap between your data system and Gatsby.  In my case, I have a Rest API in my CMS.  If you've used the gatsby-node.js file with exports.onCreateNode (more on that later), you'll be familiar with a Source Plugin, the latter is just beefier.  The Source Plugin makes calls to the CMS via the API and creates nodes that populate Gatsby's GraphQL data layer, which is then queried and used to create the site.

GETTING STARTED

We first need to install the Gatsby cli, this is done by installing it via npm/yarn:

npm i -g gatsby-cli

Copy

Once the gatsby-cli is installed, you can then create a new site:

gatsby new mysite
cd mysite
gatsby develop

Copy

You can also create a site based off of a starter template:

gatsby new mysite https://github.com/gatsbyjs/gatsby-starter-blog

Copy

This process will create a runnable site which has a few example pages, but past that it's pretty bare.  We'll be changing that dramatically.

MY REQUIREMENTS

I built a site which has a lot of functionality, so my list of requirements was pretty large.  Here's the majority of the functional pieces that I needed to figure out how to handle with Gatsby:

  • Home page
  • 404 (page not found) page
  • 301 redirects from old site that this was replacing
  • Landing pages
  • Article / Post pages
  • Account Management / Gating (login, register, update account info, password reset, etc)
  • Simple Ecommerce (using React)
  • Site Search
  • Dynamically sized images
  • Multiple google tag manager accounts
  • robots.txt
  • sitemap.xml pages
  • Good SEO
  • Forms

Ok, so this is more than just a simple blog, though it's not the most difficult site either.  Let's work on the setup. To handle a lot of these items, I needed to install several plugins:

gatsby-plugin-google-gtag: for handling multiple google tag manager tags
gatsby-plugin-robots-txt: for creating a robots.txt file
gatsby-plugin-sitemap: for creating a sitemap.xml
react-helmet,
react-schemaorg,
gatsby-plugin-react-helmet: for all installed for SEO
antd: for forms functionality (choose your favorite)
my own custom source plugin handled the data from the CMS.

My headless CMS actually handles the sourcing of the ecommerce, content, form, search and image data.  Dynamically resizing images are based on this post.

INITIAL CONFIGURATIONS

There are several Gatsby config files that need to be edited when you start a new site.  Here are the ones I updated with the reasons I updated them.

gatsby-config.js
This is the main config file where you can update various metadata for your site as well as where you add in any options and configurations for the various gatsby plugins you need.  Each plugin has its own settings, so you'll have to visit each plugin's home page to see the options.

This is different than the package.js file.  The package.js file is where you can see which plugins are installed.  They could be gatsby plugins or generic plugins.  gatsby-config is specifically to tell Gatsby which gatsby plugins to use and the options they need.

gatsby-browser.js
I use a React context in order to handle state for certain dynamic sections of the site.

// wraps site in SiteProvider
import SiteProvider from "./src/context/SiteContext";
export const wrapRootElement = SiteProvider;

Copy

The wrapRootElement allows you to wrap any Component around the entirety of your site.  This comes in handy if you want to use React Context or any other Component.

gatsby-ssr.js
This page allows you to edit the pages that are generated when you use Server-Side Rendering (ssr).

I needed to add several html tags to the head section of the generated pages.  In gatsby-ssr.js I was able to do that by using the onRenderBody API.  I also wrapped the ssr pages:

// wraps site in SiteProvider
import SiteProvider from "./src/context/SiteContext";
export const wrapRootElement = SiteProvider; 

// use onRenderBody API to insert tags into the header
export function onRenderBody({ setHeadComponents }) {
    setHeadComponents([<script src="https://sometag.com"></script>]);
}

Copy

gatsby-node.js
This page is the main brain for creating the dynamic pieces your site.  While files in the /pages folder create static pages, this is where you programmatically create pages based on data that has been stored in GraphQL (which was loaded from your source plugins).

In the page, you'll make calls to createPages, which will resemble the following:

exports.createPages = ({ graphql, actions }) => {
    const { createPage, createRedirect } = actions;

    return new Promise((resolve) => {
        // query all the Content to get the slugs in order to build the pages (slug = url)
        graphql(`
            {
                content: allDynamicContent {
                    edges {
                        node {
                            slug
                            title
                            body
                        }
                    }
                }
            }
        `).then((result) => {            
            const posts = result.data.content.edges;

            // build out a page for each content item
            posts.forEach(({ node, index }) => {
                createPage({
                    path: node.slug,
                    component: path.resolve("./src/templates/pages/post.js"), // template logic
                    context: {
                        slug: node.slug,
                    },
                });
            });
        });    
}

Copy

You could have many queries in the graphql section and many calls to createPage, as well as many components (templates) to create a different UI for each group of items.  For example, if your site has blog posts and products, you would have a post.js and a product.js in your /templates folder.  Each template could have different functionality and embedded components to suit your needs.

There are several pages on the site where the user needs to be logged in to view.  In order to make this happen, we need to implement client-only routes.  This can be done by adding the following to the gatsby-node.js

// makes pages available to match on the client
exports.onCreatePage = async ({ page, actions }) => {
    const { createPage } = actions;

    // page.matchPath is a special key that's used for matching pages only on the client.
    if (page.path.match(/^\/app/)) {
        page.matchPath = `/app/*`;

        // Update the page.
        createPage(page);
    }
};

Copy

Then create a file in the /pages directory and name it app.js.  You can use whatever logic you need, but since my site has a fair bit of logic behind a logged in area, I used the app.js as a router page and split off functionality from there:

// app.js
import React from "react";
import { Router, Route } from "@reach/router";
import Dashboard from "../components/app/dashboard";
import Profile from "../components/app/profile";
import Checkout from "../components/app/checkout";
import Order from "../components/app/order";
import Login from "../components/app/login";
import PrivateRoute from "../components/app/privateRoute";

const App = () => (
    <Router>
        <PrivateRoute path="/app/checkout" component={Checkout} />
        <PrivateRoute path="/app/dashboard" component={Dashboard} />
        <PrivateRoute path="/app/order/:orderid" component={Order} />
        <PrivateRoute path="/app/profile" component={Profile} />
        <Login path="/app/login" />
    </Router>
);

export default App;

Copy

AUTHENTICATION

The app.js handles the routing to the login page, but you may have noticed a component called "PrivateRoute".  This is a component that will kick out a user if they are not logged in and redirect them to the login page.  It can look something like this:

// PrivateRoute.js
import React from "react"
import PropTypes from "prop-types"
import { navigate } from "gatsby"
import { isLoggedIn } from "utils/auth"

const PrivateRoute = ({ component: Component, location, ...rest }) => {

  if (!isLoggedIn() && location.pathname !== `/app/login`) {
    // If we’re not logged in, redirect to the login page.
    navigate(`/app/login?to=${location.pathname}`)
    return null
  }

  return <Component {...rest} />
}

PrivateRoute.propTypes = {
  component: PropTypes.any.isRequired,
}

export default PrivateRoute

Copy

WRAPPING IT UP

Ok, now we have handled data sourcing, authentication and gating, basic configuration, plugin setup and handling the standard Gatsby pages. Now all that needs to happen is to create the various pages, templates and styles.  No problem…  Happy coding!