Building a Portfolio Site in Gatsby

 

Contents

Why Gatsby?

I first heard about static site generators way back in 2017, when I was first looking at building my own personal site.

Having had a few attempts at building WordPress websites over the years using off the shelf themes, and finding it incredibly buggy and cumbersome; I was interested to check out these new static sites as a more lightweight, flexible, and altogether more trendy alternative.

Gatsby immediately struck me as something I’d be eager to try, as built on the highly popular React framework, it would be something that would allow me to develop my burgeoning JS skills, as well, in addition to coming with all the typical bells and whistles of static sites generators such as Hugo, Jekyll, Eleventy and the rest.

It would only be in 2020, when we were all became perpetually locked indoors, that I’d finally get round to building one for myself; and having gone through the whole process, I can confirm, that it is indeed worth the fuss.

Not only is the lightning fast speed, and enhanced security that all static sites are noted for present, but Gatsby itself has some features specific to the framework that make it a dream to work with.

Most notably its excellent documentation (probably one of the few times I’ve been able to use the actual docs themselves rather than having to endlessly scour the internet for other people’s human explanations of how to do things).

And in addition, Gatsby’s Plugin infrastructure is truly fantastic. While on WordPress there’s seemingly millions of similarly named plugins that all accomplish things with varying degrees of success; instead with Gatsby, there will often just be one main Plugin, built by the community, that just works right out of the box.

Gatsby Basics

The beauty of Gatsby is that it is, at it’s core, just React but with some additional bells and whistles specific to the framework that make it fantastically simple to use.

Plugins

The main elements as I alluded to our the Plugin infrastructure which makes adding new features such as turning your site into a PWA, building a custom sitemap, making your images responsive, an absolute breeze.

Some of the main ones used in this project were:

Server Side Rendering

The other nice addition that unlike a regular Create React App, server side rendering is built into it by default.

This is done at build time when you’re putting it into production, and will just be the default for every page on the website.

Rather than say just on the initial page load with isomorphic SSR or placing the burden on the server to prerender these pages, this is all done upfront without the need to bankrupt anybody.

The downside of this is that by putting more effort in at the preparation stage build times can be Sloooooooooooow. However, this itself already seems to have been resolved with the introduction of Incremental Builds.

That rather than taking hours or minutes, can largely all be done and dusted within a few seconds - negating one of the main reasons against adopting Gatsby as a platform.

GraphQL

On top of this, Gatsby was one of the few frameworks that’s wholeheartedly adopted the GraphQL query language as its data source. And much like Gatsby itself, the beauty of this query language is in it’s simplicity.

  • Very simple querying: getting the data is just a series of nested JSON objects, and arguments should you need them, work just like a regular function.
  • Ordinarily a REST API would typically spit out a bunch of data (a lot of it not needed).
  • GQL allows you to build an API on top of your existing database / API.
  • Allows you to query and return only the data you need, making your queries much faster.

So this is ideal because it not only makes querying and utilising your data in your pages much simpler and quicker, but it vastly reduces the overhead when running these queries.

Whatsmore, all of your queries are executed at build time, so none of the burden will placed onto users, when waiting for a page to load, as they’ll have all ready been run and used to generate the static HTML.

So with all that said - and having expounded the many virtues of Gatsby as a platform - lets get into the weeds of how I went about building this particular site.

Initial Setup

Node.js

When starting on this project I was stuck at home using a work laptop, so if you’re similarly imprisoned without Admin rights on your own machine, here’s how to get the latest version of Node up and running on a Windows machine:

  1. Download the Windows Binary x64 .zip file from the downloads page - version 12+ at the time of writing (needed for the Gatsby CLI).
  2. Extract the files in the zip folder + note down where you saved the Node.exe file.
  3. Fire up the terminal a set the path to node with the following:
set PATH=C:\Users\cool.guy\YourPath\node-v12.16.2-win-x64;%PATH%
  1. Check that you’re running the correct version of the Node with:
node -v

All being well it should return version ^12.0.0, and we’re good to go.

Gatsby CLI

Likewise the Gatsby command line interface requires Node 10+ so make sure you’ve downloaded Node / completed the previous steps if not. Once you’ve done that, simply run:

npm install -g gatsby-cli

And then add the following to the package.json to:

"scripts": {
  "build": "gatsby build",
  "develop": "gatsby develop",
}

So that when you run your NPM scripts it’s executing these two gatsby commands.

GraphQL Playground

The other thing you’ll want to do is change the interface you’ll be interacting with when first querying your Gatsby data on the server.

Out of the box Gatsby uses tool called GraphiQL which, not only being tackily named, has an interface that looks like something Bill Gates might have barfed out in the late-90s.

So to get around this we’ll be using the far cooler and more spy-like interface of GraphQL Playground.

To do this we’ll simply install the cross environment package to our project:

npm install --save-dev cross-env

And add a few more extra things to our scripts object in the package.json:

"scripts": {
  "build": "cross-env GATSBY_GRAPHQL_IDE=playground gatsby build",
  "develop": "cross-env GATSBY_GRAPHQL_IDE=playground gatsby develop",
}

Now when we go to http://localhost:8000/___graphql to query our data we’ll be met with the far more aesthetically pleasing GQL Playground instead.

my first graphql query

Note: this also provides a handy feature to examine your API using the Docs tab, as well as telling you what data types and arguments it accepts for each one in Schema section (so definitely worth checking out).

Components

Now to get onto the actual website bit of this post. I’m using the Gatsby Starter Lumen theme - which saved me the bulk of the hassle in getting set up - while giving me a nice Medium-esque looking blog to build upon.

However, it still needed a couple additions, so here we’ll cover what extra components I added, and more broadly what’s needed when building your own in Gatsby.

Essentially, when adding any additional components to your Gatsby site there are 3 main parts to it:

1. Page Template - This is usually where your component will be dropped into. This is where your new element will be rendered and will eventually be propagated. Alternatively you may have a similarly named (see: post-template.js & Post.js) component that lives inside of the template which pretty much populates the entirety of the content on that page.

Additionally if you’re passing down data via props, then this (or the equivalent component) will be where you’ll pass down any data that you may need (such as frontmatter etc).

2. Data - At the same time the Template is where you’ll want to be writing your GraphQL queries to gain access to any data from your database that you’ll want to use in either this template any of the child components that descend from this tempalte page.

3. Component - The actual component itself, this is where you’ll write your JSX as either a class or stateless functional component, and is essentially what will render out all your HTML or CSS (whether you’re using Sass or Styled Components to do this).

So with all that in mind let’s look at the first few components we added to this site:

<CoverImage />

For this I wanted a cool header image, in the vein of Polygon or the Verge where it takes up a large proportion of the above the fold section. (Is it a cliche? Yes. Do I still like it? Again, yes.)

So for this I used a nifty plugin called:

Which is effectively tailor made for this use case, as it allows you to create a header element where the main hero image is defined in the CSS using the background-image CSS property, allowing you to overlay your main H1 on top of it.

Not only this but it also applies all of the cool and srcSet elements and attributes to said image, ensuring that it’s both correctly sized, lazy loading, and fully responsive - all with virtually no setup. You simply drop it in and you’re good to go.

Frontmatter

All this requires is adding an additional property to our frontmatter in our markdown file, which will tell it what our main hero image is going to be:

---
title: Some Random Post
date: "1973-04-29T12:01:00.000Z"
template: "post"
cover_image: ../../static/media/my-sweet-new-image.png
---

The path will have to be the one that connects the markdown file you’re writing in to wherever your images are stored (in my case I have foolishly not put them in the same folder as the markdown file itself meaning I’ve got to move up numerous parent directories in order to get to it - something to resolve in phase 2).

GraphQL Query

Then we need to query this new image file, via our post-template.js file, to ask for the cover_image:

So here we’re adding the cover_image property to our GQL query, which thanks to installing Gatsby Image + Background Cover Image, now has nested within it the childImageSharp property (which is responsible for all of the image transformations) and the “fluid” property which contains our array of srcSet images at various resolutions contained within the <picture> tag.

So in order to gain access to the cover_image inside our Post.js component, we’ll first destructure out this value into just { data } and then pass it down to our <Post /> component with data.post (the query to grab the frontmatter is named post in this instance).

This will then pass it all the information that we’ve asked for in our “post” GQL query including our frontmatter, as well as our newly optimised cover image.

# post-template.js

const PostTemplate = ({ data }: Props) => {
  const { title: siteTitle, subtitle: siteSubtitle } = useSiteMetadata();
  const { frontmatter } = data.post;
  const { title: postTitle, description: postDescription, socialImage } = frontmatter;
  const metaDescription = postDescription !== null ? postDescription : siteSubtitle;

  return (
    <Layout title={`${postTitle} - ${siteTitle}`} description={metaDescription} socialImage={socialImage}>
      <Post post={data.post} related={data.related} />
    </Layout>
  );
};

export const query = graphql`
query PostBySlug($slug: String!, $category: String!) {
  post: markdownRemark(fields: { slug: { eq: $slug } }) {
    frontmatter {
      title
      description
      date
      cover_image {
        childImageSharp {
          fluid(maxWidth: 1600) {
            ...GatsbyImageSharpFluid
          }
        }
      }
    }
  }
}
`

From post-template.js we’re then passing the cover_image down one level to the <Content /> component, and passing it this data using the image prop.

# Post.js
const Post = ({ post, related }: Props) => {
const { title, date, description, category, tags, cover_image, socialImage } = post.frontmatter;
  return (
    <div className={styles['post__content']}>
      <Content body={html} title={title} image={cover_image} />
    </div>
  );
};

<Content /> Component

Finally once we’re done prop drilling, we finally get to actually implementing the component. So now we’re inside the Content component where we simply import BackgroundImage and drop it into our content component like so. Utilising the image prop that we’ve passed it to fill the fluid value that Gatsby Image / BackgroundImage uses:

# Content.js
import React from 'react';
import BackgroundImage from 'gatsby-background-image';
import styles from './Content.module.scss';

const Content = ({ body, title, image }: Props) => (
  <div className={styles['content']}>
    <BackgroundImage
      Tag="section"
      className={styles['content__header']}
      fluid={image.childImageSharp.fluid}
    >
      <div className={styles['content__layer']}>
        <h1 className={styles['content__title']}>{title}</h1>
      </div>
    </BackgroundImage>
    <div className={styles['content__body']} dangerouslySetInnerHTML={{ __html: body }} />
  </div>
);

export default Content.js;

Nice! So that’s our hero image sorted on our posts.

Next, we’ll set about adding a proper navigation to the site. And for this I’ve mainly implemented one using this excellent guide.

And to very quickly explain how it works, it’s essentially using some boolean logic to check whether the Nav bar is open or not, and if so, to show the full open NavBar, and if not, to show the Hamburger component instead.

This is all primarily done using the useState() hook (allowing us to access state inside a regular functional component).

Inside of this we set two parameters, one to declare navbarOpen as the initial state, and setNavbarOpen to the state we want to change it to. This allows us to then use the ternary operators to display different blocks of code based on whether the navbarOpen has been set to true or false by our onClick() event in the <Toggle /> component.

Note: we don’t have a state object declared in this (functional) component so our useState() hook is set to false by default.

So then in our component we use setNavbarOpen() to change navbarOpen to false so the onclick event in the toggle returns the open Hamburger menu instead of the closed one.

There will also be a couple other components to define the main logo, and the links that will go in the navigation themselves, but otherwise that’s it, pretty nifty!

import React, { useState } from 'react';
import NavLinks from './NavLinks';
import Logo from './Logo';
import styled from 'styled-components';

const Nav = () => {

  const [navbarOpen, setNavbarOpen] = useState(false);

  return (
    <Navigation>
      <Logo />
      <Toggle
        navbarOpen={navbarOpen}
        onClick={() => setNavbarOpen(!navbarOpen)}
      >
        {navbarOpen ? <Hamburger open /> : <Hamburger />}
      </Toggle>
      {navbarOpen ? (
        <Navbox>
          <NavLinks />
        </Navbox>
      ) : (
        <Navbox open>
          <NavLinks />
        </Navbox>
      )}
    </Navigation>
  );
}

export default Nav;

Next, I wanted to add a very basic related articles component a) for the Googlez and b) so there’s actually some content underneath the main post itself.

First, we’ve got to make a query that asks for the 3 most recent blog posts that fall within the same category as the article you’re on.

Gatsby-node.js

To do this we pass this “related” query to the post-template via context in the gatsby-node.js.

In my case I’m actually using a file called create-pages.js that is referenced in the gatsby-node.js file but most of the time this is where the logic will be written. And I’ve just added the category property to the GQL query I’m initially pulling out and saving to the result object.

This is so that we can then extract the category from the fields object in the GQL query itself, and then pass this in using the context object, when gatsby-node is creating all the ‘post’ pages at build time.

This in turn, allows us to use $Category as an argument in our GraphQL query in the post-template.js meaning that we can then return the 3 most recent articles to use in our Related Articles component.

# gatsby-node.js

const { edges } = result.data.allMarkdownRemark;

_.each(edges, (edge) => {
  if (_.get(edge, 'node.frontmatter.template') === 'page') {
    createPage({
      path: edge.node.fields.slug,
      component: path.resolve('./src/templates/page-template.js'),
      context: { slug: edge.node.fields.slug }
    });
  } else if (_.get(edge, 'node.frontmatter.template') === 'post') {
    createPage({
      path: edge.node.fields.slug,
      component: path.resolve('./src/templates/post-template.js'),
      context: {
        slug: edge.node.fields.slug,
        category: edge.node.frontmatter.category,
      },
    });
  }
});

Then in our post-template we’re now writing a second query underneath our main post query, called related using allMarkdownRemark() and passing it in our newly enabled $category argument. Allowing us pass the data we get back from “related” down to the <Post /> component using data.related.

# post-template.js

const PostTemplate = ({ data }: Props) => {
  const { title: siteTitle, subtitle: siteSubtitle } = useSiteMetadata();
  const { frontmatter } = data.post;
  const { title: postTitle, description: postDescription, socialImage } = frontmatter;
  const metaDescription = postDescription !== null ? postDescription : siteSubtitle;
  // const { relatedArticles } = data.related.edges;

  return (
    <Layout title={`${postTitle} - ${siteTitle}`} description={metaDescription} socialImage={socialImage}>
      <Post post={data.post} related={data.related} />
    </Layout>
  );
};

export const query = graphql`
  query PostBySlug($slug: String!, $category: String!) {
    post:  { ... }  // Leaving this out for brevity
    related: allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC},
      limit: 3,
      filter: {
        frontmatter: {
          category: {
            eq: $category
          }
        }
      }
    ) {
      edges {
        node {
          frontmatter {
            title
            date
            description
            category
            socialImage
            cover_image {
              childImageSharp {
                fluid(maxWidth: 400, maxHeight: 400) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
          fields {
            slug
            categorySlug
          }
        }
      }
    }
  }
`

Then we can simply drop in our <RelatedArticles /> component into Post.js, passing it in the related object that we’ve destructured out from our GQL query. And then we’ll just write some logic to say if related.edges.length is true, then return the <RelatedArticles /> and if not don’t return anything.

# Post.js

const Post = ({ post, related }: Props) => {

  const { html } = post;
  const { internal: { content } } = post;
  const { wordCount: { words } } = post;
  const { tagSlugs, slug } = post.fields;
  const { title, date, description, category, tags, cover_image, socialImage } = post.frontmatter;

  return (
    <div className={styles['post']}>
      <div className={styles['post__content']}>
        <Content body={html} title={title} image={cover_image} />
      </div>

      <div className={styles['post__footer']}>
        <Meta date={date} />
        {tags && tagSlugs && <Tags tags={tags} tagSlugs={tagSlugs} />}
        <Author />
      </div>

      <div className={styles['post__comments']}>
        <Comments postSlug={slug} postTitle={post.frontmatter.title} />
      </div>

      /* Related Articles Component */
      <div>
        {related.edges.length && <RelatedArticles articles={related.edges} /> || null }
      </div>
    </div>
  );
}

Now once we’ve got the data side of things all pulling through correctly, we can then set about building the component.

As you can see above we’re passing in the related article edges array which will contain each of the article nodes that contains all the information on each post that we’ve queried.

So we’ll pass this down via the articles prop, and simply map over the array of those 3 returned articles.

# RelatedArticles.js

import React from 'react';
import ArticleCard from './ArticleCard/ArticleCard';
import styles from './RelatedArticles.module.scss';

const RelatedArticles = ({ articles }) => (
  <>
    <h3 className={styles['related-articles-heading']}>Related Articles</h3>
    <section className={styles["related-articles"]}>
      {articles.map((article, i) => (
        <ArticleCard {...article.node} key={i} />
      ))}
    </section>
  </>
);

export default RelatedArticles;

And then inside of that we’ll actually style each of those individual Article Card’s (again using our good friend BackgroundImage) to return three tiles, with the image of the blog post, the title, category, and publish date.

And then we’re just using a bit of CSS Grid to give it that block layout, as well as keeping things responsive across different device widths.

# ArticleCard.js

import React from 'react'
import { Link } from 'gatsby'
import BackgroundImage from 'gatsby-background-image'
import styles from './ArticleCard.module.scss'

const ArticleCard = ({ frontmatter, fields }: Props) => {

  const { title, description, date, category, cover_image, socialImage } = frontmatter;
  const { slug, categorySlug } = fields;

  return (
    <article className={styles['article-card']}>
      <Link to={slug}>
        <BackgroundImage
          className={styles['article-card__image']}
          fluid={cover_image.childImageSharp.fluid}
        >
          <div className={styles['article-card__layer']}>
            <Link className={styles['article-card__category']} to={`${categorySlug}`}>{category}</Link>
            <Link  to={slug}>
              <h4 className={styles['article-card__title']}>{title}</h4>
              <p className={styles['article-card__date']}>{date.split('T')[0]}</p>
            </Link>
          </div>
        </BackgroundImage>
      </Link>
    </article>
  );
}

export default ArticleCard;

And that’s it! A nice and simple <RelatedArticles /> component. I did consider doing something more sophisticated with machine learning / NLP etc, but just to get something up and running quickly this seemed to be the far easier solution (and ought to make good material for a future blog post).

Lastly, I just added a simple footer component mainly using Flexbox and a metric-crapload of

‘s. Then we’re just populating the social links using the useSiteMetadata() function and destructuring out all the social links contained in our main config.js file.

const Footer = () => {
  const { author: { contacts: { facebook, twitter, instagram } } } = useSiteMetadata();
  return (
    <div className={styles['bottom']}>
      <footer className={styles['footer']}>
        <div className={styles['footer__container']}>
          <div className={styles['footer__row']}>
            <div className={styles['footer__col_logo']}>
              <h2 className={styles['footer__logo_wrap']}>Oliver Hayman</h2>
              <p className={styles['footer__logo_text']}>Technical SEO Consultant in London</p>
            </div>
            <div className={styles['footer__social_links']}>
              <h4 className={styles['footer__nav_title']}>Follow</h4>
              <div className={styles['footer__nav_items']}>
                <a rel="noopener" href={`https://www.facebook.com/${facebook}`}>Facebook</a>
                <a rel="noopener" href={`https://twitter.com/${twitter}`}>Twitter</a>
                <a rel="noopener" href={`https://www.instagram.com/${instagram}`}>Instagram</a>
              </div>
            </div>
            <div className={styles['footer__main_links']}>
              <h4 className={styles['footer__nav_title_second']}>Editorial</h4>
                <div className={styles['footer__nav_items']}>
                  <Link to="/">Blog</Link>
                  <Link to="/pages/about">About</Link>
                  <Link to="/pages/contact">Contact</Link>
                </div>
            </div>
          </div>
        </div>
      </footer>
    </div>
  );
}

export default Footer;

And then, same as the <Nav /> we’re just dropping this into our Layout.js file so that it’s now rendered across every single page:

# Layout.js
const Layout = ({ children }: Props) => {

  return (
      <>
        <Nav className="nav" />
          <div className={styles.layout}>
            {children}
          </div>
        <Footer />
      </>
  );
};

Schema.org

Finally, to placate the great $GOOG, I added an <ArticleSchema /> component to pull the frontmatter data into JSON+LD format.

And this essentially done first by building a loose shell of the Article schema object, and then just passing in all the values we need with template literals. Before finally using JSON.stringify() and passing it into a <script> tag and inserting it into the <head> using React <Helmet>.

# ArticleSchema.js
import React from 'react';
import Helmet from 'react-helmet';
import PropTypes from 'prop-types';
import * as config from '../../../../config';
import TextCleaner from 'text-cleaner';

const ArticleSchema = ({ slug, title, description, template, date, image, content, category, wordCount }) => {

  const cleanText = TextCleaner(content).stripHtml().condense().valueOf();
  const { url, author } = config;

  const schemaOrgJSONLD = [
    {
      '@context': 'https://schema.org',
      '@type': 'Article',
      'mainEntityOfPage': {
        '@type': 'WebPage',
        '@id': `${url}${slug}`,
        'speakable': {
        	'@type': 'SpeakableSpecification',
          'cssSelector': [
            'Content-module--content__title--1qFLI',
            'Content-module--content__body--2bfha'
          ]
        }
      },
      'headline': title,
      'description': description,
      'datePublished': date,
      'articleBody': cleanText,
      'articleSection': category,
      'wordCount': wordCount,
      'image': {
        '@type': 'ImageObject',
        'url': `${url}${image}`,
      },
      'publisher': {
        '@type': 'Organization',
        'name': `${author.name}`,
        'logo': {
          '@type': 'ImageObject',
          'url': `${url}${author.photo}`
        }
      },
      'author': {
        '@type': 'Person',
        'name': `${author.name}`,
        'sameAs': [
          `https://www.facebook.com/${author.contacts.facebook}`,
          `https://twitter.com/${author.contacts.twitter}`,
          `https://www.linkedin.com/${author.contacts.linkedin}`,
          `https://github.com/${author.contacts.github}`,
          `https://www.instagram.com/${author.contacts.instagram}`
        ],
      }
    }
  ];

  return (
    <Helmet>
      <script type="application/ld+json">
        {JSON.stringify(schemaOrgJSONLD)}
      </script>
    </Helmet>
  );
};

export default ArticleSchema;

Then we’ll just drop this into our Post.js file, with all the values we need taken from the frontmatter.

# Post.js
const Post = ({ post, related }: Props) => {
  const { html } = post;
  const { internal: { content } } = post;
  const { wordCount: { words } } = post;
  const { tagSlugs, slug } = post.fields;
  const { title, date, description, category, tags, cover_image, socialImage } = post.frontmatter;

  return (
      <div className={styles['post']}>
        <ArticleSchema
          slug={slug}
          title={title}
          description={description}
          date={date}
          image={socialImage}
          content={content}
          category={category}
          wordCount={words}
        />
        <div className={styles['post__content']}>
          <Content body={html} title={title} image={cover_image} />
        </div>
        /* Other stuff goes here... */
      </div>
  );
};

export default Post;

And we’re all set!

And that’s pretty much it for the basic changes I wanted to make to the initial template.

I may add some additional stuff in the future such as Breadcrumbs, internal site search, a contact form and some other nifty features, but I felt this got me to 90% of the way there, before I felt I actually needed to start writing some content for this thing.

Plugins

One more thing, before we get into deploying the site, a quick word on plugins, like many things in Gatsby these things are a dream to work with.

Gatsby Image

By far the coolest, and most impressive, plugin you can add is the Gatsby Image plugin. Essentially what this does is it takes all of your images and make them truly responsive, adding lazy loading, compression, a range of different sizes for various viewports, turning them into WebP files, this thing’s really got it all.

Implenting it could not be easier as well, you simply

npm i gatsby-image --save

And add it to the top of your component with: import Img from 'gatsby-image' update your GQL queries in your templates (same as what was done with the BackgroundImage component), to ask for your newly modified images then this will auto-magically add some properties when you’re querying your image in your GraphQL API to add childImageSharp and the various transform properties, and then you can simply pass this value into the image component with something like:

<Img fluid={image.childImageSharp.fluid} />

And hey presto, you’ve got some lightning fast, responsive images already set up!

Gatsby MDX

Next, you can apply this same functionality to your inline images in your markdown files, easily as well.

You just install the Gatsby MDX plugin with:

npm i gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react --save

And assuming you’ve already got Gatsby Image installed, and you’re pointing gatsby-source-filesystem to the folder where your images are kept; all the inline images in your markdown posts should instantly have the Gatsby Image benefits applied to them without having to do anything else.

All the same markdown image syntax is kept the same i.e. ![My Image](../../static/media/my-new-image.png) and they will now all be lazy loaded. The only thing you may have to do is point it to the relative path instead of just ‘/media/my-new-image.png’.

# gatsby-config.js
{
  resolve: `gatsby-plugin-mdx`,
  options: {
    gatsbyRemarkPlugins: [
      {
        resolve: `gatsby-remark-images`,
        options: {
          maxWidth: 640,
        },
      },
    ],
  },
},

React Helmet

This a great package, created by the Devs at the NFL and this will allow you to SSR all your important meta tags to the <head> of the page.

npm i gatsby-plugin-react-helmet react-helmet --save

So this is where you’ll want to include all of your basic SEO tags, as well as things like Twitter Cards and Facebook Open Graph tags. And somewhat counter-intuitively (but also very handy) you can just drop the <Helmet /> tag anywhere in your component and it will just be added to the <head>.

# Layout.js
<>
  <Nav className="nav" />
  <div className={styles.layout}>
  <Helmet>
    <html lang="en" />
    {/* SEO Tags*/}
    <title>{title}</title>
    <meta name="description" content={description} />
    <meta name="robots" content="max-image-preview:large" />
    {/* OGP Tags & Twitter Cards */}
    <meta property="og:site_name" content={title} />
    <meta property="og:image" content={metaImageUrl} />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:creator" content="@OliverHayman1" />
    <meta name="twitter:title" content={title} />
    <meta name="twitter:description" content={description} />
    <meta name="twitter:image" content={metaImageUrl} />
    <meta name="twitter:image:alt" content={title} />
  </Helmet>
    {children}
  </div>
  <Footer />
</>

Sitemap

This was a great addition, as you just pass it a GQL query to return all your site’s URLs, you can pass it some regex to exclude any templates you don’t want, and then it just loops through all your pages into the required XML format.

npm i gatsby-plugin-sitemap --save
# gatsby-config.js
{
  resolve: 'gatsby-plugin-sitemap',
  options: {
    query: `
      {
        site {
          siteMetadata {
            siteUrl: url
          }
        }
        allSitePage(
          filter: {
            path: { regex: "/^(?!/404/|/404.html|/dev-404-page/|/tag/|/tags|/categories|/page/)/" }
          }
        ) {
          edges {
            node {
              path
            }
          }
        }
      }
    `,
    output: '/sitemap.xml',
    serialize: ({ site, allSitePage }) => allSitePage.edges.map((edge) => ({
      url: site.siteMetadata.siteUrl + edge.node.path,
      changefreq: 'daily',
      priority: 0.7
    }))
  }
},

Robots.txt

npm i gatsby-plugin-robots-txt --save

This was a really neat little plugin, that allows you to define different robots files depending on your environment, so your staging or dev environment can be set to block everything, while your production build can be set to allow Googlebot and other robots.

# gatsby-config.js
{
  resolve: 'gatsby-plugin-robots-txt',
  options: {
    host: siteConfig.url,
    sitemap: `${siteConfig.url}/sitemap.xml`,
    env: {
      development: {
        policy: [{ userAgent: '*', disallow: ['/'] }]
      },
      production: {
        policy: [{ userAgent: '*', disallow: '' }]
      }
    }
  }
},

Google Analytics / Tag Manager

Again this is super simple you just install the plugin and add your Universal Analytics tracking code to the gatsby-config.js and your done. :)

npm i gatsby-plugin-google-gtag --save
# gatsby-config.js
{
  resolve: 'gatsby-plugin-google-gtag',
  options: {
    trackingIds: [ siteConfig.googleAnalyticsId ],
    pluginConfig: {
      head: true,
    },
  },
},

Deployment

So now that we’ve got most of our basic components added, now it’s time to set this sucker live!

Deployment was also super simple, like everyone else and their grandmother, I am also using Netlify for deployment, because it’s great.

The setup is a breeze you just need to connect it to your Github repo, tell it what your build command is in your package.json, and point it in the direction of your production build folder (much of which it will automatically detect) and you’re done!

The only other thing you may need to do is if you’re using a file-based config in a netlify.toml file, is declare those same build commands there as well.

# netlify.toml
[build]
  publish = "public"
  command = "npm run build"

Then it’s simply a case of logging into Netlify, telling it where to find your Git repo, adding the build commands, and then setting it live.

Netlify Deployment Config

The other great thing is that now whenever you push a new commit to your repo this change will be automatically propagated to your live production build as well, massively reducing the time between making dev changes and sending these live your public facing site.

Netlify HTTPS Certificate

And that’s it! My - and hopefully soon - your, very own Gatsby blog to pen your musings on.

Thanks for reading! ✨