Gatsby JS: Build a static website with React

Kalizi <Andrea>
Frontend Weekly
Published in
18 min readJan 7, 2021

--

During May 2020 I was asked to review and send my CV to a colleague for an official project presentation, so I got straight to my Google Drive account, where I have my CV, and started a revision to fix project dates and add things I’ve done since my last revision.
Most of my projects were under NDA because were private, I had just a few stuff online and visible for anyone wanted to revise my work. At a certain point, I got my eye on my personal email. I won’t write my old email here (I don’t like spam), but its format was email(at)libero.it , that domain ending stunned me and I thought “damn, why I still have this email and I don’t have a custom domain?”.

So I got to __fill_this_with_your_fav_provider__ (I don’t want to advertise a provider or another) and bought a domain, I set up a custom email and I thought “what about building a website?”.

The technology

An advantage of developing for yourself is that you don’t have to justify your choices to anyone: you want to approach that new technology that’s actually used by just ten people in the world? You can. Do you want to stick to that technology stack that you’re comfortable with? No problems. Just be coherent with your choices. In my case I wanted speed, my website had to be amazingly fast.

Programmer speed paranoid meme: from reddit
Programmers speed paranoid, from Reddit

Why fast? The thing I heard the most in latest years was “network connections are getting faster everywhere, so you don’t have to worry about web resources you load in your website” and my thought about this sentence is that everyone thinks that is WRONG. If you build a website just throwing stuff like framework and libraries inside of it you’ll increase the stuff that will be loaded by the user, and every file loaded by the user can be seen as time and resource handled by a server or more servers that will slow the page rendering or loading for the final user. This doesn’t mean you don’t have to use frameworks or libraries, everyone has to think which is the perfect trade-off between features and load time.

In my case, I just wanted that my website was made of just a few things to load to have a very short rendering time and what’s the fastest way to serve a website? Pure HTML with little CSS and JS. But that made me turn up my nose. I’m a fan of trying new stuff that I don’t know, and I love React, so Googling stuff I found Gatsby.

Gatsby is a web framework that helps to build fast static websites with React. You can build any kind of websites by pairing Gatsby with APIs. But the main focus is: being fast.

Gatsby loads only critical parts of the page, so your site loads as fast as possible. Once loaded, Gatsby prefetches resources for other pages so that clicking on the site feels incredibly fast. Gatsby lets teams focus on creating content-driven websites instead of worrying about performance.

When I read this on Gatsby’s website I was like 😍, so I decided to pick Gatsby and learn new technology.
You aren’t still convinced about Gatsby? Gatsby documentation keeps me covered with a paragraph called “Convincing Others”, you can read that!

The setup

Gatsby has great documentation and great support for developers. I started reading, I learned the basics but I wanted to be productive as fast as possible, so what’s the best way to start? Just… start!

npm install -g gatsby-cli

Now gatsby is available between your commands, start your new project.

gatsby new your-project-name https://github.com/gatsbyjs/gatsby-starter-hello-world

This will clone the gatsby-starter-hello-world into your project folder. This is just a basic starter, if you want to spend less time architecting your code, you can use a Gatsby Starter from the starter page.

Now enter in your folder and start your project:

gatsby develop

This will start a dev server at localhost:8000 where you’ll see your website progress while developing. Setup is ready, now talk a little about features and code structure.

The project features: Gatsby plugins

Photo by Joel Rohland on Unsplash

Gatsby helps your development with lots of plugins available out-of-the-box that you can integrate with a little configuration. Let’s talk of some plugins available that I’ll use later:

  1. Gatsby Source Filesystem: this will help you read data (images, JSON files, CSV or other data) from the filesystem and use it to build your site.
  2. Gatsby SASS: write SCSS/SASS, Gatsby will output CSS.
  3. Gatsby Google Fonts: don’t stick with default browser fonts, check Google Fonts, pick some fonts you think are nice and use them in your website for free. (Bonus tip: aren’t you a Typographer like me? Checkout FontPair).
  4. Gatsby Image: this will optimize images loading. Did I talk about speed yet? (This needs two plugins more for image sharpening)
  5. Gatsby React Helmet: this will help by editing what’s usually is not editable with React, the header tag content (title, metatag, etc…).
  6. Gatsby Sitemap: generate a sitemap for search engines.
  7. Gatsby Google Analytics: I don’t really think this needs description.
  8. Gatsby i18n: multi-language made easy. (While I’m writing this, the gatsby page about i18n doesn’t work well, but you can find the i18n at npm)
  9. Gatsby Smoothscroll: because smooth scrolling makes everything fashionable.

So with all this plugin ready to get in, let’s stress npm a little:

npm i gatsby-plugin-google-analytics gatsby-plugin-sitemap gatsby-plugin-react-helmet react-helmet gatsby-image gatsby-transformer-sharp gatsby-plugin-sharp gatsby-plugin-google-fonts node-sass gatsby-plugin-sass gatsby-source-filesystem gatsby-plugin-i18n gatsby-plugin-smoothscroll --save

We’ll deep into plugins configuration while building the website.

The basic: from Hello World to Hello Gatsby

Now we have a src folder where can put stuff. A great gatsby feature is that if you put a file in src/pages you’ll have the relative page online: if you create src/pages/contact.js you’ll have the /contact route online!

So start structuring the project by making these directories:

  1. components: where we will put our reusable React Components (ex: buttons, icons).
  2. views: where we will put our page view components (ex: nav, search bar with suggestions).
  3. styles: where we will put our page styles in SCSS (or SASS if you prefer).

For styling, I decided to pick the SCSS version of Bootstrap npm i bootstrap-scss --save. To build SCSS, you have to add the SASS plugin to the config adding it to the plugin array in gatsby-config.js:

'gatsby-plugin-sass',

This will automatically build sass when you import an scss file in your JS files. So you can start building your SCSS.

One of the first things to do while setting up a stylesheet is to pick fonts, fonts I wanted are Montserrat (Sans-Serif, Body) and Inconsolata (Monospace, Title) from Google Fonts. For speed you can’t just import a font, you have to tweak the configuration of the Gatsby Prefetch Google Fonts (inside the plugin array in gatsby-config.js):

{
resolve: 'gatsby-plugin-google-fonts',
options: {
fonts: [
`Montserrat\:300,400,600`,
`Inconsolata\:400,900`,
],
},
}

Now your fonts will be automatically loaded and available in CSS, and font-family ‘Montserrat’ and ‘Inconsolata’ are ready.

You can structure your SCSS as you wish, pick your framework, use your structure, everything you want, just a tip: every time you call an import, that file is built separately, so if you like variables as much as I do, put them in a separate file and import them everywhere you need them.

I aimed to build a simple one page, so I started with the navbar creating src/views/Navbar.js where you’ll put the Navbar component. How should I write here? Gatsby is based on React, pick your preferred syntax and start typing. I’ll use the function syntax with Hooks and Effects, you can pick your preferred syntax.

import React from 'react';
import '../styles/navbar.scss';

const Navbar = () => {
return (
<nav>
<div className="menu">
#1
</div>
<div className="kalizi">
Kalizi
</div>
<div className="menu">
#2
</div>
</nav>
);
};

export default Navbar;

As you can see, this is nothing new if you’ve ever worked with react, just a class with code.

Now take our navbar and put them into our home, let’s open pages/index.js and tweak it:

import React from 'react';
import Navbar from '../views/Navbar';

import '../styles/index.scss';

const Home = () => {
return (
<>
<Navbar />
<div>Hello from Gatsby!</div>
</>
)
};

export default Home;

In this component, I used the empty tag <></> that’s a shorthand for React.Fragment. If your development server is up (started with gatsby develop), you should see your navbar up and a nice “Hello from Gatsby!”.

Building up sections

I started creating a welcome section in views/home/WelcomeSection.js and the relative stylesheet in styles/home-sections.scss where to put all the styles for the home sections. Let’s tie up some stuff: to start building the welcome section I wanted two images to be loaded, a photo to show up myself, and a background to make the page fashionable. Load an image is a common practice nowadays, I could just put a 4000x3000 5MB jpg file into my public asset folder, embed it via a <img src="/assets/mybigfile.jpg"> tag and let someone browser struggle to load it… but that was not my purpose, I wanted to exploit the Gatsby image plugin that helps you loading different version of the same image based on which client’s connecting thanks to the <picture> tag that handles different source sets. Before going forward, I’ve created an assets folder into the src folder and put there the image to be loaded.

While in development mode Gatsby image will load images from the filesystem, querying it via GraphQL. This means that the first thing we have to do is let GraphQL discover our assets folder using Filesystem plugin. To load the plugin, go back again to our gatsby-config.js and add another object to our plugins array:

{
resolve: 'gatsby-source-filesystem',
options: {
name: 'assets',
path: `${__dirname}/src/assets/`,
},
}

This object tells Gatsby to load the filesystem plugin, the filesystem plugin will read the options and will generate as many source nodes as the files into the directory passed as “path”. If you want to look at the files getting loaded you can query them via Gatsby built-in graphiql, accessible at localhost:8000/__graphql. Once you’re in you can launch a query to get all files indexed (I won’t deep into how the query is written, I just stick with the one available at the documentation page):

query AssetsQuery {
allFile {
edges {
node {
name
extension
relativePath
modifiedTime
}
}
}
}

This will return every node (file indexed) the filesystem plugin found in the folder, in my case the result was:

{
"data": {
"allFile": {
"edges": [
{
"node": {
"name": "kalizi",
"extension": "jpg",
"relativePath": "kalizi.jpg",
"modifiedTime": "2020-12-07T22:56:37.766Z"
}
},
{
"node": {
"name": "keyboard",
"extension": "jpg",
"relativePath": "keyboard.jpg",
"modifiedTime": "2020-12-06T23:34:52.796Z"
}
}
]
}
},
"extensions": {}
}

Now that files are correctly indexed, they can be loaded into the webpage! For this purpose, we need to tell gatsby to load the files into our Home component by querying, so open pages/index.js and add the query by loading graphql:

import React from 'react';
import {graphql} from "gatsby";
// other omitted imports
const Home = ({data}) => {
// code here
}
export default Home;
export const query = graphql`
query {
kaliziImage: file(relativePath: { eq: "kalizi.jpg" }) {
childImageSharp {
fixed(width: 256, height: 256) {
...GatsbyImageSharpFixed
}
}
}
bgImage: file(relativePath: { eq: "keyboard.jpg" }) {
childImageSharp {
fluid(maxWidth: 1920, quality: 100) {
...GatsbyImageSharpFluid
}
}
}
}
`;

That’s a lot of stuff together, let’s take a moment to split and comment what’s there:

  • we loaded graphql via import: nothing more to say
  • we added an export of a constant named query using the tagged template: this will be loaded from Gatsby during the build phase that will perform the query, get the results and inject them in our Home component using the data attribute of our props, this perfectly explains the next point.
  • we added the props restructuring in the Home component to take data attribute: there we will find our images.
  • the whole query contains two subqueries for two files (kaliziImage and bgImage) that will be loaded and processed for sharpening: the first is a static image, so I wanted to sharpen it to a fixed dimension to adapt it the same way on every device, this can be achieved using the fixed clause; the second is a background image, that can’t be fixed but must be resized fluently into its container, this can be achieved using the fluid clause.

If you want to know more about how the whole process works, you can get a deeper explanation into the Gatsby documentation page.

From here, we’ll have our images available into the data parameter, I wanted to use them into my sections so I just passed them as props again:

<WelcomeSection bgImage={data.bgImage.childImageSharp.fluid} kaliziImage={data.kaliziImage.childImageSharp.fixed} />

And embedded them in the Welcome Section:

import React from 'react';
import Img from 'gatsby-image';
// other imports
const WelcomeSection = props => {
return (
<section className="welcome-section">
<div className="bg-space">
<Img fluid={props.bgImage} />
</div>
<div className="about-me">
<Img fixed={props.kaliziImage} />
// other code
</div>
</section>
);
};

Please note that this will just load the images, if you want to show them nicely, you have to get your hands dirty with CSS. If you want to know all the properties of the Img tag, again: documentation.

Now images are responsive, what about making everything multilanguage? We must exploit gatsby-plugin-i18n. Start creating a folder for translations and an index file where to put site languages (in my case English and Italian) and the default language src/i18n/index.js:

module.exports = {
langs: ['en', 'it'],
defaultLangKey: 'en'
};

Then, again, let gatsby-config know we want to use multilanguage:

const languages = require('./src/i18n');module.exports = {
plugins: [
// other plugins configuration
{
resolve: 'gatsby-plugin-i18n',
options: {
langKeyForNull: 'any',
langKeyDefault: languages.defaultLangKey,
useLangKeyLayout: true,
prefixDefault: false,
}
}
]
};

This will handle languages path like /en for your website, to handle this, while creating a file you just need to append the locale for that file, so our index must be split into two files index.en.js for /en and index.it.js for /it. This website doesn’t need a lot of content, so for translations, I’ll stick with some JSON files to be loaded through a require.
So I created a src/i18n/it/home.json and src/i18n/en/home.json, and populated them with my content:

{
"welcome": {
"developer": "Backend&App Developer",
...
}
}

On my index.en.js I added the relative require:

const translations = require('../i18n/en/home');

And I passed translations to each component:

<WelcomeSection translations={translations.welcome} ... />

Where I just printed each string with a normal string printing. Is this the best solution? I don’t really know, in this case, is probably the fastest to get online but gatsby has a lot of options, a lot of plugins and the compatibility with i18next and react-intl, so if you need more, adapt your project in a different way, it’s all up to you!

After building up the first section, I started building a second section called “Works section”, so again, build a component in views/home/WorksSection.js and render it into index.[locale].js passing translations.

Do you remember we talked about smooth scrolling? That’s the moment to add it! Open again our gatsby-config.js and add it to the available plugins:

'gatsby-plugin-smoothscroll',

How does it work? It gives you a scrollTo function to be called where you want to trigger the scroll, I wanted that trigger to be in the nav, with menu divs, so in my Navbar.js I added:

import scrollTo from 'gatsby-plugin-smoothscroll';
// other code
const Navbar = props => {
return (
<nav>
<div className="menu" onClick={() => scrollTo('.welcome-section')}>

</div>
<div className="kalizi">
Kalizi
</div>
<div className="menu" onClick={() => scrollTo('.work-section')}>
#2
</div>
</nav>
);
};

The scrollTo syntax is like the querySelector so the selector given in must be unique to be sure that the scroll will be where you want it to be!

The result while writing was this:

First section development

A little comment about this:

  • I like purple, nothing more to say about this.
  • Images are loaded via the relative plugin, this means that loading is blazingly fast, and all manipulations about putting the image as the background for half of the page or adding the radius was done via CSS.

Setting up Analytics

Photo by Stephen Phillips - Hostreviews.co.uk on Unsplash

Talk truly: every time you put a website online and you care about that, you start looking at every analytics system you can or any debug system you have installed to check what’s happening and what users are doing with it!

I just considered Google Analytics for this instance, but you can check for other plugins to install if you prefer (for example Yandex Metrika). Configuring analytics is really easy, you just add options to the plugins array in gatsby-config:

{
resolve: `gatsby-plugin-google-analytics`,
options: {
trackingId: "yourtracking-id",
head: false,
anonymize: true,
respectDNT: true,
exclude: [],
pageTransitionDelay: 0,
// Defers execution of google analytics script after page load
defer: false,
sampleRate: 5,
siteSpeedSampleRate: 10,
cookieDomain: "your-domain.ext",
},
},

You can tweak any of these options, in my case, I just didn’t want the loading to be deferred and the IPs to be anonymized. If you want to tweak all the options, you must check the documentation. Nothing more, you put this and you’re ready to go!

An eye on SEO

If you’ve ever worked with React, you know that one of the best way to handle stuff on the <head> tag is to use React Helmet, in Gatsby, we have the Gatsby plugin React Helmet for that.

First of all, We start from defining metadata about the websites into our config to fetch them as a graphql query and structure them as JSON-LD

We have to build our website metadata and put them into our gatsby-config:

module.exports = {
siteMetadata: {
pathPrefix: '/',
title: 'Website title',
titleTemplate: " | Basic template for completing the title",
titleAlt: 'This is your alternative title',
description: 'Your website description',
headline: 'Website headling',
url: 'https://yoursite.ext',
siteUrl: 'https://yoursite.ext',
defaultLanguage: 'en',
logo: '/assets/logo.jpg',
// JSONLD / Manifest
favicon: '/assets/favicon.png', // Used for manifest favicon generation
shortName: 'ManifestName', // shortname for manifest.
author: 'Your Name', // Author for schemaORGJSONLD
themeColor: '#FFFFFF',
backgroundColor: '#000000',
// Facebook Attributes
facebook: 'Sharing title',
ogLanguage: 'it_IT',
},
plugins: [
// Here goes the plugins definition
]
};

There’s a lot of data, and now? Create a component folder and a file in src/components/SEO.js:

That’s a big component, it fetches data from our config via the GraphQL query and posts that data via Helmet in the header tag using web standards format (you can discover more here). Everything in that component is highly customizable, you can tweak almost everything to achieve what you want to be published.

If you also want to add a Favicon, let’s build a Favicon component and embed it into the comment on the SEO component:

You can also do something really basic for Facebook open-graph:

import React from 'react';
import { Helmet } from 'react-helmet';

const Facebook = ({ url, name, type, title, desc, image, locale }) => (
<Helmet>
{name && <meta property="og:site_name" content={name} />}
<meta property="og:locale" content={locale} />
<meta property="og:url" content={url} />
<meta property="og:type" content={type} />
<meta property="og:title" content={title} />
<meta property="og:description" content={desc} />
<meta property="og:image" content={image} />
<meta property="og:image:alt" content={desc} />
</Helmet>
);

export default Facebook;

Finally, now that your components are ready to go, open your index.[locale].js and add

import SEO from '../components/SEO/SEO';
...
<SEO siteLanguage="_YOUR_LOCALE_" />
...

Please note: this is a possible implementation, almost surely not the best, so feel free using anything different from this, you can use for example gatsby-plugin-seo, the components from gatsby-theme-seo, the components from gatsby-seo-example, or you can just be creative and write your own component.

Another nice stuff for web indexing is the sitemap. Gatsby knows all your pages, so we can let it generate the sitemap automatically through the sitemap plugin, in my particular case the sitemap will not be so long and I don’t need any tweaking so adding the gatsby-plugin-sitemap to the gatsby-config.js will do the work, but if you want more you can read the plugin doc!

Not found 🛑

What if… you open a non-existing page like localhost:8000/aaaaaa ?

Gatsby development 404

We should expect this result, it’s a normal HTTP 404 error and gatsby helps us customizing it, again, there’s gatsby documentation for that. We just have to add a file: src/pages/404.js. I just leave here a basic page:

import React from 'react';
import Navbar from '../views/Navbar';

const navTranslations = require('../i18n/en/navbar');

const NotFound = () => {
return (
<>
<Navbar translations={navTranslations} />
<p style={{ paddingTop: '70px', color: 'white', textAlign: 'center', fontWeight: 700 }}>
DEAD PAGE HERE ☠️
</p>
</>
);
};

export default NotFound;

That will produce:

Now that 404 works, it’s up to you to customize it, Gatsby will parse it and build a 404.html to be set as static 404 to be served from your web server or hosting.

The deploy

Photo by Lenin Estrada on Unsplash

Now we’re ready to go, publish is just a mere stone’s throw from here! Stop the development server launched with gatsby develop. Some packages sometimes do a mess with gatsby cache, so the first thing to do is a cache cleaning with the command

gatsby clean

Now the last step, build the production version via

gatsby build

Gatsby has a nice guide about how to get your site live. The build time will depend on your hardware, in my case it just took 21.2506825 sec, the curious fact is that you can see everything about the build process:

success open and validate gatsby-configs - 0.041s
success load plugins - 0.838s
success onPreInit - 0.030s
success delete html and css files from previous builds - 0.005s
success initialize cache - 0.005s
success copy gatsby files - 0.073s
success onPreBootstrap - 0.014s
success createSchemaCustomization - 0.006s
success Checking for changed pages - 0.002s
success source and transform nodes - 0.105s
success building schema - 0.333s
info Total nodes: 53, SitePage nodes: 1 (use --verbose for breakdown)
success createPages - 0.004s
success Checking for changed pages - 0.001s
success createPagesStatefully - 0.075s
success update schema - 0.020s
success onPreExtractQueries - 0.001s
success extract queries from components - 0.391s
success write out redirect data - 0.004s
success onPostBootstrap - 0.001s
info bootstrap finished - 5.393s
success run static queries - 0.043s - 1/1 23.29/s
success run page queries - 0.214s - 4/4 18.71/s
success write out requires - 0.042s
success Building production JavaScript and CSS bundles - 13.014s
success Rewriting compilation hashes - 0.003s
success Building static HTML for pages - 2.363s - 4/4 1.69/s
success Generating image thumbnails - 15.759s - 15/15 0.95/s
success onPostBuild - 0.010s
info Done building in 21.2506825 sec

This process will output a public folder with all your files. Now the website is ready, you just have to take the content of the public folder and publish it on your hosting. You can use a managed hosting if you don’t know how to set up your architecture, you can use a low-cost VPS if you want to set up your custom software stack or you can move to something like Github Pages or Heroku. Everything coming after the deploy is up to you!

Gatsby production version audit

Want to test your website for common web practices and to check if it’s inline with the most common web standards? Chrome got you covered with Lighthouse, an extension for auditing a website/web application focusing on Performance, Accessibility, Best practices, SEO and PWA.

The first test comes after building, you clean your cache with gatsby clean, you build your production version with gatsby build and start the testing server for the production version with gatsby serve. The website is now available at localhost:9000, to audit it with Lighthouse you just have to open the Developer Tools and jump to the Lighthouse extension, now pick the options you want to test, the version (Mobile/Desktop) and let Lighthouse generate the report. In my case, for mobile, with all options checked the result is this:

Lighthouse report for local `gatsby serve` server

In my opinion, the results were good. PWA result is zero because the website wasn’t designed to be a PWA. The full detail about the performance score was mainly focused on:

  • HTTP2 was not implemented → it’s a local test server, that’s ok.
  • No caching policies → same here, it’s a test server.

Now take the built version and put it online into an optimized environment, in my case I put the website into a VPS with Nginx (configured to cache static content for a long time) backed by Cloudflare. The results of putting the website into this stack were this:

Lighthouse report for deployed server with tweaked Nginx and Cloudflare

These performance results are obviously better than the local results because the test was done in an optimized environment. Trying the auditing on the Desktop version the performance increases to 99 instead of 97. If you want to get all 100 out of 100, you have to check every detail described in the report, but again, that’s up to you, for me, these levels were good!

--

--

Kalizi <Andrea>
Frontend Weekly

IT Engineering Bachelor and still student 🎓 Mobile&Backend Dev 💻 Growth hacking Enthusiast ☕ Startupper 🚀 Metalhead 🤘🏻 With love, from Palermo ❤️