Making a Static Website with Gatsby, Contentful, and Netlify
Note: This article was originally written in late 2017, and the Medium release date has updated for some reason.
One of the nice things about the holidays is the chance to work on side projects and dabble around with new technologies. Coming up with a new website for one of my music projects has been on the list for ages, so I decided to finally get it done and learn something new while at it.
I’m going to assume that you’re more or less familiar with modern JavaScript tools and frameworks such as Yarn and React, and have at least heard of GraphQL. Setting up the project doesn’t require in-depth knowledge in any of these, but as you move to building your layouts etc. basic knowledge of React and JavaScript in general is necessary.
Key takeaways of the article include:
- Using Gatsby’s starters
- An overview of creating content on Contentful
- Querying content and assets from Contentful using GraphQL
- Making simple pages, layouts and components
- Programmatical page creation in Gatsby
- Setting up automatic deployments in Netlify, triggering from GitHub and Contentful
If you just want to skip to the code, check out the project repo on Github.
What you’ll need to follow:
- A GitHub account (or Bitbucket, Gitlab since that’s what Netlify supports)
- A Contentful account (the free tier should suffice)
- A Netlify account (again, free tier)
- Basic understanding of React and modern JavaScript
- Your favourite code editor, e.g. Atom or Visual Studio Code
- Yarn or NPM, Node.js etc.
The Spec
- Project setup, a skeleton site, and writing this article shouldn’t take more than a couple of days (I’m a slow writer)
- The skeleton site should have a landing page, a handful of subpages, a simple image gallery, and blog posts
- JavaScript-based and low-maintenance
- Fast, flexible, and fun to develop
- Easy-to-use backend, so that non-technical people can update content (and not bother me with everything)
- Mobile friendly
- Cheap (i.e. generous free tiers), since it’s a personal project
The Solution — Gatsby, Contentful, and Netlify
I’ve never had much love for Wordpress, Drupal, and other full-fledged CMS’s. Most of the time they’re either too much, too little or simply a pain to develop on (which is subjective, of course). What I wanted was a headless CMS, a separated frontend, and an automated deployment pipeline to a hosting provider that I wouldn’t have to maintain. Stumbling upon Why I created my blog with Gatsby and Contentful by Fernando Poumian, I figured the stack might be worth a shot.
Gatsby — A React-based static site generator
Static site generators are nothing new: Jekyll, Hugo, and many others have been around for ages. I’ve been looking for a JS-based generator for a while but in many cases, the ones I’d run into didn’t seem mature enough. Lauded for speed and flexibility, and based on React, GatsbyJS looks like an interesting candidate.
Contentful — CaaS & Headless CMS
Initially, I ran into Contentful at work as our eCommerce development team began benchmarking headless CMS’s to manage content on our storefronts. I’ve been waiting for a change to give it a try myself, as the API’s and the whole user experience looks promising, at least on the surface level.
The free tier Developer account is limited to 2 locales per space, 10K records, 1M API calls and 1TB in bandwidth which should be more than enough for a small site. You’ll need to display the Contentful logo attribution in e.g. your footer. The Developer+ account that allows you to remove the attribution is 39€/mo., and more records, locales, users etc. start at 249€/mo.
Netlify — Simple Hosting for Static Sites
I love dabbling with servers and providers such as DigitalOcean and AWS have been great when doing something more complex or simply weird. However, for a simple static site, the maintenance of a full server or the hassle of setting up stuff on AWS makes little sense. Netlify, among others, offers a tempting set of features with a generous free tier. Netlify also offers Netlify CMS that could serve as a backend, but Contentful seems more robust and flexible.
The free tier account gives you pretty much all you need. The paid tiers starting at 45€/mo. offer more unlimited shared sites, user roles, and access control, unless you go full Enterprise and get a bag of goodies including private CD environments, SSO, integrations etc.
How it’s going to work
In the end result, we want Netlify to build and deploy on two events:
- The repository master branch gets updated on GitHub
- The data has changed on Contentful
Setting up Gatsby
If you’re familiar with static site generators or CMS’s, setting up Gatsby is a breeze: Install the CLI, generate a skeleton, start developing.
- Install Gatsby’s command line tool
yarn global add gatsby-cli
- Create a new site
gatsby new your-site
- Access the directory
cd your-site
Using the Gatsby’s default starter template, the skeleton was generated in under 40 seconds using my phone’s hotspot. Not bad.
Looking at the generated project structure, the setup looks familiar and relatively clean with a ton of configuration at the root and the bulk of your code in ./src/
:
gatsby-browser.js
gatsby-config.js
gatsby-node.js
gatsby-ssr.js
LICENSE
node_modules/
package-lock.json
package.json
README.md
src/
— layouts/
— — index.css
— — index.js
— pages/
— — 404.js
— — index.js
— — page-2.js
yarn.lock
Essential commands
gatsby develop
starts a hot-reloading development environment accessible at localhost:8000gatsby build
performs an optimized production build for your site generating static HTML and per-route JavaScript code bundles.gatsby serve
starts a local HTML server for testing your built site.
Run gatsby develop
and open up localhost:8000
in your browser of choice. Hello World!
Add GitHub
If you haven’t done so already, hit the project up with a GIT repository on e.g. GitHub. We’ll be needing it later to trigger deployments to Netlify. If you’re unfamiliar with GitHub or version control in general, check out the GitHub Guides for information and tutorials.
Building layouts and components
If you’re not familiar with React or the concept of components, I recommend you take a look at the React documentation or the official React tutorial. As Gatsby is built on React, basic understanding of it is recommended when developing a site.
By default, Gatsby starts you off with a few examples in ./src/layouts
and ./src/pages
, and in larger or more complex projects it’s probably a good idea to split your code into other utility folders as well.
As you can guess, layouts are for your general layout templates and pages handle the content.
Setting up our index page
We’re going to keep things simple, so I’ll want my index page to display my gallery, a list of my pages and a list of my posts. It won’t be fancy, but get’s the job done. Open up src/pages/index.js
and replace the contents with the following:
import React from 'react'
import Link from 'gatsby-link'
import Gallery from '../utils/gallery'const IndexPage = () => (
<div>
<h1>Hi people</h1>
<h2>Here's my gallery:</h2>
<Gallery />
<h2>Here are my subpages:</h2>
<ul>
<li><Link to="/subpage/">A subpage</Link></li>
</ul>
<h2>Here are my posts:</h2>
<ul>
<li><Link to="/blogpost/">A post</Link></li>
</ul>
</div>
)
export default IndexPage
Right now we’re just doing some quick placeholder templating and we’ll follow up later with connecting our data. Next, we’ll create the placeholder component for our Gallery.
The Gallery component
On our index page, we’re referencing a Gallery component that doesn’t exist yet. Let’s create a new folder, /src/templates
and a file called /src/templates/gallery.js
, and put the following code there:
import React from 'react'
const Gallery = () => (
<div>
<ul>
<li><img src="//picsum.photos/300/200/?random" alt="alt text" /></li>
</ul>
</div>
)
export default Gallery
We’ll be using the templates directory later on for our page and post templates as well. Nothing fancy so far, but now we have something to look at on the site. We’re done with our placeholder templating for now, so let’s move on to creating the actual content on Contentful that we’ll be connecting to our site later.
Modeling Content Types and Creating Content on Contentful
Contentful organizes your data with Content Models, Content Entries, and Assets and wraps them up in a Space.
Let’s start by creating a Space on Contentful.
After creating the space, we’re greeted by a friendly Getting Started -page, which also outlines what we’re going to do next in Contentful:
Defining the Structure
Let’s pause for a minute to think about what content we’re going to have on the site:
- Multiple Pages
- An Image Gallery
- Multiple Blog Posts
For the sake of simplicity, we’ll keep the models minimal. We could easily add more fields to the models for SEO, authors, and what not, but this will do for now. Let’s move forward with the following minimal models:
Page
- Page Title
- Page Content
Blog Post
- Post Title
- Post Content
Next, let’s add some fields to our Page — a Title and a Content field.
Contentful offers us a good amount of field types with their respectful settings, validations, and appearance settings. Since we’re dealing with titles and content, let’s go with a Text field.
Looking good. We’ll want to configure some validation as well, so go with Create and Configure.
On the Validations tab, you’ll find all the nice stuff we can do with text inputs. Let’s make Title a required field, save our changes, and move on. Create another field in the Page model, named Content. This time, make it a long-text -field. After you’re done with setting up the content type, hit Save in the top-right corner of the screen.
Let’s click on Content Model on the main navigation to see a list of… our Content Models:
Let’s hit the big blue Add content type -button and create our Blog Post content model, again with Title and Content fields.
Finally, let’s create a Content Model for our Gallery. Like before, we’ll add a Title field. We’ll also want to add images to our Gallery, so we’ll want to use an other type of field here: Media.
We’re going to want our Gallery to contain multiple images, so check the Many files -option and hit Create and Configure.
I’m going to be boring and allow only images in my gallery. Contentful supports a good amount of different assets & attachments from images to spreadsheets, video, and presentations, so you should be able to support pretty much anything there.
We’ll do one more thing to make the editors’ lives a bit nicer — display the attached images as a gallery on Contentful. Select the Appearance tab for our options.
Asset Gallery sounds like a nice way to display image assets in the editor, so let’s go with that and Save our changes to both the field and the model like before.
Let’s add some content!
We’ll start by adding a couple of pages and posts, so let’s hit that big blue button and select Page from the flyout.
Contentful’s editor is a pretty straight-forward markdown editor with media inserts and a preview, and supports drafts as you’d expect. Create a couple of pages (e.g. About Us, My Favourite Craft Beer, Coffee is life) and a few Blog Posts.
What about the Gallery?
Contentful handles media as assets that are then linked into a record, such a Gallery entry. We can either link existing assets or upload them one-by-one as we create our entry. Since we’re working with an empty space and need multiple images for our Gallery, let’s hit Media on the main navigation for that sweet bulk upload and select Multiple Assets from the flyout.
Contentful offers a ton of integrations for importing assets, ranging from Dropbox to GitHub, Facebook, and taking a picture on the go. I’ll be more old-fashioned and just upload a bunch of stock photos I downloaded earlier from Picsum (they have a lot of nice quality stuff there and a placeholder API, take a look).
The bulk uploads default to a draft status, so we’ll need to publish them. Luckily we can do that in bulk as well.
Finally, let’s head back to Content to create our Gallery entry:
In the editor, we can either Create a new asset or Link existing assets. Since we already uploaded our images, we’ll go with the latter.
Select all the images and hit Insert. For larger sites with more existing assets, the multi-select and search are nice features. Once the assets have been linked, we can alter their order by dragging the images from the … -drag handle.
Finished with content
Finished with creating the content models and the actual content, we now should have a couple of Pages, some Blog Posts, and a Gallery entry.
You can check out the example content and models using Contentful’s Discovery App. If you’re feeling adventurous, you can mess around with Contentful’s import tools using the space export included in the GitHub repository of this article.
Connecting Contentful as a Data Source
Gatsby can connect to a ton of data sources using plugins, ranging from filesystem assets to 3rd party services. There’s also a data source plugin for pulling types, entries, and assets from Contentful. But first, let’s generate some API keys in Contentful:
Setting up Contentful API keys
Under APIs, we can generate Content Delivery and Content Management tokens. We’ll want to generate a Content Delivery API key for our website, so find that friendly blue button, Add API key under the Content delivery / preview tokens -tab and give it a gentle push.
Upon key generation, Contentful displays a few things:
- Space ID — The unique ID for your space
- Content Delivery API -access token — The access token for your published data
- Content Preview API -access token — This access token is used for previewing unpublished content such as drafts
We’ll need the Space ID and Content Delivery API -access token in the next section, where we set up the data source in Gatsby.
Setting up the Data Source
Let’s start by installing the data source plugin for Contentful:
- Run
yarn add gatsby-source-contentful
to install the package - Head to your
gatsby-config.js
to finish the configuration
// In your gatsby-config.js, add
plugins: [
{
resolve: `gatsby-source-contentful`,
options: {
spaceId: `your_space_id`,
accessToken: `your_access_token`,
},
},
];
In general, it’s not a good idea to store our credentials directly in the config file. Gatsby kind of supports environment variables out of the box. Just add .env.development
and/or .env.production
or store your production variables on your host (e.g. Netlify) and the underlying Webpack DefinePlugin embeds them during the build. However, if you need to access the variables in your Node.js scripts, you’ll need to import them with dotenv — which is what we’ll need.
So, let’s create a .env.development
file with the following content:
# In your .env.development
CONTENTFUL_SPACE_ID=your_space_id
CONTENTFUL_ACCESS_TOKEN=your_access_token
Then, install dotenv by running yarn add dotenv
and head back to gatsby-config.js
to configure it. The Node environment isn’t set in development mode, so we’ll need to set the path manually. I’m going to omit handling .env.production
since we’ll handle the production variables in Netlify.
# At the top of your gatsby-config.js
if (process.env.NODE_ENV === ‘development’ || !process.env.NODE_ENV) {
require(‘dotenv’).config({ path: ‘./.env.development’})
}
Finally, modify gatsby-source-contentful’s options ingatsby-config.js
to use the environment variables instead of hard-coded values:
// In your gatsby-config.js again
options: {
spaceId: `${process.env.CONTENTFUL_SPACE_ID}`,
accessToken: `${process.env.CONTENTFUL_ACCESS_TOKEN}`
}
We still don’t want our credentials to end up in version control, so create or open your .gitignore
and add the environment variable files to the list of ignored files:
# In .gitignore, add the following
.env.production
.env.development
.env
Now you should be able to pull your data from Contentful using GraphQL queries, as described in the Gatsby documentation.
GraphiQL, the included in-browser GraphQL IDE
One of the things I loved was GraphiQL, Gatsby’s included in-browser IDE for data exploration and query building. While running gatsby develop
, head to http://localhost:8000/___graphql
(that’s three underscores). The interface will help you craft your GraphQL queries more efficiently and familiarise you with basic GraphQL queries in general.
Start typing a new query by opening brackets { }
and then, while inside the brackets, hit Ctrl-Space
to open up that beautiful thing called auto-complete:
The auto-complete gives us a list of all the GraphQL-accessible data sources connected to Gatsby. We’re interested in the data coming from Contentful, so let’s try things out by selectingallContentfulPage
and hitting Ctrl-Enter
to run the query:
When you run the query, GraphiQL fills in a couple of blanks by adding edges, nodes and an id. However, that’s not really enough for our needs; we need titles and content as well. Point your cursor inside the node-bracket of your query, add a new row, and open up the auto-complete again with Ctrl-Space
— boom, there we have our options. Let’s select title and content. We’ll need to add a subquery for content, so change content
to:
...
content {
id
content
}
...
Then hit Ctrl-Enter
to run your query and you should see something similar to this:
Now that we know how to make and test our GraphQL-queries, we can move on the the juicy bit: Dynamic content creation.
Programmatical page creation in Gatsby
Now we get to the fun stuff. Creating pages manually is often good enough when you don’t have a lot of content and can rely on editors knowing how to code, but in our use case that won’t work.
Generating pages
Open up your gatsby-node.js
.
const _ = require(`lodash`)
const Promise = require(`bluebird`)
const path = require(`path`)
const slash = require(`slash`)
// We'll need something for creating the slugs
const slugify = require(`slugify`)
const slugifyOptions = {
replacement: '-',
remove: /[$*_+~.()'"!\-:@]/g,
lower: true
}
// We'll use Gatsby's createPage API for generating the pages
exports.createPages = ({ graphql, boundActionCreators }) => {
const { createPage } = boundActionCreators
return new Promise((resolve, reject) => {
// This is where we'll do most of our work
})
}
We start by requiring and configuring some helpers we’ll need in a bit: Promises, paths and slash to keep them sane, slugify for generating our slugs, and lodash for general developer happiness.
Let’s move on and edit the contents of our promise:
...
return new Promise((resolve, reject) => {
// We'll do most of our work here
graphql(
`
{
allContentfulPage(limit: 1000) {
edges {
node {
id
title
}
}
}
}
`
)
.then(result => {
if (result.errors) {
reject(result.errors)
}
// We'll do the actual page creation here
resolve() // Resolve the promise
})
})
...
Here we start off by making a GraphQL query and pull all of our pages from Contentful, respecting Contentful’s limit of 1000 returned edges. At this point all we need are the internal ID and page title.
Then, we move on to handling our query result, creating the actual pages and finally resolving our promise. But how do we actually create the pages?
...
.then(result => {
if (result.errors) {
reject(result.errors)
}
// We'll do the actual page creation here
const pageTemplate = path.resolve(`./src/templates/subpage.js`)
_.each(result.data.allContentfulPage.edges, edge => { // Here's the beef, seitan, or whatever rocks your boat:
createPage({
path: `/pages/${slugify(edge.node.title, slugifyOptions)}/`,
component: slash(pageTemplate),
context: {
id: edge.node.id
},
}) })
resolve() // Resolve the promise
})
...
Put together, your gatsby-node.js
should now look something like this:
Great, now our generation code is there, but that’s not going to work without our template file for pages.
Create src/templates/subpage.js
and add the following to try things out:
import React from 'react'
import Link from 'gatsby-link'const SubPage = () => (
<div>
<h1>Subpage Title</h1>
<p>Content here</p>
<Link to="/">Go back to the homepage</Link>
</div>
)export default SubPage
This is good enough for testing that our page generation works. Assuming you gave one of our pages the title “My Favourite Craft Beer”, you should be able to open http://localhost:8000/pages/my-favourite-craft-beer/ and see our template with placeholder content. We’ll want to display our actual data, so let’s make a some edits to our template:
import React from 'react'
import * as PropTypes from "prop-types"
import Link from 'gatsby-link'
const propTypes = {
data: PropTypes.object.isRequired,
}
class SubPage extends React.Component {
render() {
return (
<div>
<h1>Subpage Title</h1>
<p>Content here</p>
<Link to="/">Go back to the homepage</Link>
</div>
)
}
}
export default SubPage
First we import React prop types to help us with validation. Then we make a slight change to the way we declare our SubPage as the shorthand won’t soon be enough.
...
export default SubPageexport const pageQuery = graphql`
query pageQuery( $id : String! ) {
contentfulPage( id: { eq: $id } ) {
id
title
content {
content
}
}
}
`
Now, we add a query for our template. In gatsby-node.js
we made a query to create the files, but the template will need to access more data. The pageQuery will get its $id-variable from the createPage({..., context: {...})
we supplied in gatsby-node.js
.
...
class SubPage extends React.Component {
const page = this.props.data.contentfulPage
return (
<div>
<h1>{page.title}</h1>
<div
dangerouslySetInnerHTML={{
__html: page.content.content,
}}
/>
<div><Link to="/">Go back to the homepage</Link></div>
</div>
)
}
...
Last but not least, we replace our placeholders with variables.
Put together, you should have something similar to this:
Let’s try to load the page again at http://localhost:8000/pages/my-favourite-craft-beer/, and our page content should be visible.
We’ll need to repeat this for our blog posts by adding a new GraphQL query and a createPage -handler in gatsby-node.js
and create a template.
For the sake of brevity and trying not to repeat myself too much, you can check the final results in the GitHub repo:
Are we supposed to get our user to those pages using magic, you ask?
Listing our links on our index page
Well, this ain’t Hogwarts, so we’ll need to list some links somewhere. Open up src/pages/index.js
and we’ll make change our declarations like we did with our templates:
import React from 'react'
import Link from 'gatsby-link'
import Gallery from '../templates/gallery'
class IndexPage extends React.Component {
render () {
return (
<div>
<h1>Hi people</h1>
<h2>Here's my gallery:</h2>
<Gallery />
<h2>Here are my subpages:</h2>
<ul>
<li><Link to="/subpage/">A subpage</Link></li>
</ul>
<h2>Here are my posts:</h2>
<ul>
<li><Link to="/blogpost/">A post</Link></li>
</ul>
</div>
)
}
}
export default IndexPage
Next, we’ll want to add a GraphQL query to the page, so that we’ll get enough data to render link lists.
...
export default IndexPage
export const indexQuery = graphql`
query indexQuery {
pages: allContentfulPage {
edges {
node {
id
title
}
}
}
posts: allContentfulBlogPost {
edges {
node {
id
title
}
}
}
}
`
Here we have two new things; aliases and multiple sources for our query. Now, we can create a simple component for rendering our lists of links.
...
import Gallery from '../templates/gallery'
import slugify from 'slugify'
const slugifyOptions = {
replacement: '-',
remove: /[$*_+~.()'"!\-:@]/g,
lower: true
}
const LinkList = (data) => {
return (
<ul>
{data.items.map((item, i) => (
<li key={i}>
<Link key={i} to={`/${data.basepath}/${slugify(item.node.title, slugifyOptions)}`}>
{item.node.title}
</Link>
</li>
))}
</ul>
)
}
class IndexPage extends React.Component {
...
Nothing fancy, just something that’ll get the job done. Let’s replace our placeholders with the component.
...
class IndexPage extends React.Component {
render () {
return (
<div>
<h1>Hi people</h1>
<h2>Here's my gallery:</h2>
<Gallery />
<h2>Here are my subpages:</h2>
<LinkList basepath="pages" items={this.props.data.pages.edges} />
<h2>Here are my posts:</h2>
<LinkList basepath="posts" items={this.props.data.posts.edges} />
</div>
)
}
}
...
Great, we have our lists so let’s wrap things up by adding our gallery. We’ll start by adding a few things to our GraphQL query:
...
}
gallery: contentfulGallery (title: {eq: "My Awesome Gallery"}) {
id
title
images {
id
title
sizes {
base64
aspectRatio
src
srcSet
sizes
}
}
}
}
`
Not a very optimal way of getting the gallery in question, but good enough for our scope. Contentful offers a lot more data on our assets than can be seen in the query above. The automatically generated srcSet, sizes, and so on are nice since we won’t have to worry about that. Check out Contentful’s documentation for more info, or simply try things out using the GraphiQL IDE in Gatsby.
Before we make our changes to the actual Gallery component, let’s make one more quick change in src/pages/index.js
and feed our gallery data to the component:
...
<h2>Here's my gallery:</h2>
<Gallery content={this.props.data.gallery} />
<h2>Here are my subpages:</h2>
...
Now, open up our Gallery component, src/templates/gallery.js
so we can set it up properly.
import React from 'react'
const Gallery = (data) => (
<div>
<h3>{data.content.title}</h3>
<ul>
{data.content.images.map((image, i) => (
<img
key={i}
alt={image.title}
srcSet={image.sizes.srcSet}
src={image.sizes.src}
sizes={image.sizes.sizes}
/>
))}
</ul>
</div>
)
export default Gallery
Now, the Gallery component outputs a simple list of our images, using the generated srcSets and sizes.
We’re finally done with our code and can move on to deployments. The code we built isn’t that optimized, and we could style the pages and galleries a lot, but that’s not that relevant for our example.
Triggering builds and deployments on Netlify
By now, you should have a local application that pulls content from Contentful and builds a working static site. We’ll want to make it public, so let’s move on to triggering builds and deployments on Netlify.
Creating a new site on Netlify is simple and fast, as they connect to the most common Git providers; Github, GitLab, and Bitbucket. After we’ve authenticated to our provider and select our project repository, all that’s left is to check the deployment settings that have been automatically detected from our project.
Netlify will automatically build and deploy our project to a generated URL that can be changed in the Settings. We’ll need to head there anyway to set up our environment variables and a build hook for triggering builds when we update our content on Contentful.
Next, we’ll head over to Settings → Build & Deploy for some configuration fun times.
In the long list of configuration options we can find Build environment variables, followed by Build hooks.
Let’s start by adding our variables from Contentful:
CONTENTFUL_SPACE_ID
with our Contentful Space IDCONTENTFUL_ACCESS_TOKEN
with our Content Delivery access token
After that, we need to create a Build hook for triggering on content edits. Since I like my naming conventions wild and extreme, I named mine Contentful updates.
Let’s copy that generated URL to our clipboard and head over to Contentful to set up the final webhook.
You’ll find the Webhooks under Space Settings → Webhooks. Let’s give that Add webhook -button a push.
All that we’re required to configure for the webhook is a name and the webhook URL. Paste the Build hook URL from Netlify to the URL field and type in an apt name for our webhook.
Optionally, Contentful webhooks allow us to use basic authentication, custom headers and select which events will trigger the webhook. For this project, we can just let it trigger on all events. Save the webhook.
Testing the deployments
The setup is now done, and we should have automated deployments on the following events:
- Our repository’s master branch updates on GitHub
- We create, save, archive, unarchive, publish, unpublish or delete Content Types, Entries or Assets on Contentful
Hindsight 20/20
First of all, I didn’t remember how time-consuming it is to write articles and tutorials, so I absolutely blew my time budget :)
However, the actual tech side of the project was first done in probably less than an hour and I didn’t run into any hiccups or mind-boggling bugs. Contentful and Netlify took only a minute or two to setup and both had good user experiences.
Getting started with Gatsby was a good experience as well, as I have some background in static site generators, JavaScript, and a touch of React as well. The builds ran swiftly, the built-in development tools we’re nice, the documentation was at a good level with example projects, and development with Gatsby was fun in general.
All in all, my experience with the entire stack was positive and I recommend you take a look at all the components, whether you’re just looking for a single piece for your workflow or the whole stack.
Resources
Examples
Tech
Articles