Gatsby
Getting Started with Gatsby.js
Updated: 03 September 2023
Introduction
Gatsby.js is a Static Site Generator that makes use of React and can plug into a headless CMS to generate a Static Site with SPA support and functionality
I’m using the Gatsby.js Tutorial from the documentation
Prerequisites
- Node.js
- Git
- Gatsby CLI
- Optional: Yarn
To install the Gatsby CLI you can use npm
:
1npm i -g gatsby-cli
Or if using yarn
:
1yarn global add gatsby-cli
To view the Gatsby help menu once installed:
1gatsby --help
Create New Site
To create a new Gatsby.js site run:
1gatsby new gatsby-hello-world https://github.com/gatsbyjs/gatsby-starter-hello-world
Where gatsby-hello-world
is the name of the new directory for the site and will clone from the Gatsby GtiHub template
Next cd gatsby-hello-world
and run the following command to start development server you can use the gatsby-cli
1gatsby develop
Or npm
:
1npm run develop
Or
1yarn develop
You should then be able to launch the site on http://localhost:8000/
Looking at the initial site you should see the following files:
1gatsby-hellp-world2│ .gitignore3│ .prettierignore4│ .prettierrc5│ gatsby-config.js6│ LICENSE7│ package.json8│ README.md9│ yarn.lock10│11├───src12│ └───pages13│ index.js14│15└───static16 favicon.ico
In the index.js
file you will see a simple React Component that is exported:
1import React from 'react'2
3export default () => <div>Hello world!</div>
Editing this will live update the page as you edit and save the file, this uses HMR in the background and will update you browse live
Create a New Page
Gatsby organises pages similar to the way you would if you were using normal HTML instead. Inside of the pages
directory you can create an about.js
file with something like:
pages/about.js
1import React from 'react'2
3export default () => <div>About Page</div>
And then we can add a link to this from the home
component using the React Link
component
pages/index.js
1import React from 'react'2import { Link } from 'gatsby'3
4export default () => (5 <div>6 <h1>Hello World</h1>7 <Link to="/about/">About</Link>8 </div>9)
Clicking on the Link
on the index.js
page will take you to the about.js
page
Build the Site
To build the initial site you can just run
1gatsby build
Or
1npm run build
Or
1yarn build
You can then simply deploy the public
directory using your preferred method
Adding Styles
To add styles we first need to create a src/styles/global.css
file, this will contain all the global CSS for our application - we can add some basic content to it to start off
global.css
1html,2body {3 margin: 0;4 padding: 0;5}6
7h1 {8 color: lightblue;9}
Next in the src
project root directory create a file called gatsby-browser.js
, this is one of a few standard files that Gatsby uses. In this file import the global.css
file we just created with:
gatsby-browser.js
1import './src/styles/global.css'
After adding this file you will need to restart the Gatsby development server
Now let’s create a component called Container
:
src/components/container.js
1import React from 'react'2import containerStyles from './container.module.css'3
4export default ({ children }) => (5 <div className={containerStyles.container}>{children}</div>6)
This file imports css file in the same directory called container.module.css
which is a CSS Module which means that the styles will be scoped to this component. We also use containerStyles.container
to apply the .container
class to the main element
container.module.css
1.container {2 margin: 3rem auto;3 max-width: 600px;4}
We can then update the index.js
page to use this container:
index.js
1import React from 'react'2import { Link } from 'gatsby'3import Container from '../components/container'4
5export default () => (6 <Container>7 <h1>Hello World</h1>8 <Link to="/about/">About</Link>9 </Container>10)
Plugins
Using plugins in Gatsby involves three steps:
- Installing the plugin. For example we can install the
typography
plugin with:
1npm install --save gatsby-plugin-typography react-typography typography typography-theme-fairy-gates
Or
1yarn add gatsby-plugin-typography react-typography typography typography-theme-fairy-gates
- Configuring the plugin which is done using the gatsby-config.js` file and a configuration file for the relevant plugin
gatsby-config.js
1module.exports = {2 plugins: [3 {4 resolve: `gatsby-plugin-typography`,5 options: {6 pathToConfigModule: `src/utils/typography`,7 },8 },9 ],10}
src/utils/typography.js
1import Typography from 'typography'2import fairyGateTheme from 'typography-theme-fairy-gates'3
4const typography = new Typography(fairyGateTheme)5
6export const { scale, rhythm, options } = typography7export default typography
If you inspect the output HTML now after running gatsby develop
you should see some styles in the head
which are as a result of the typography
plugin, the generated styles will look like:
1<style id="typography.js">2 ...;3</style>
Data
The Gatsby Data Layer is a feature of Gatsby that enables you to build sites using a variety of CMSs
For the purpose of Gatsby, Data is anything that lives outside of a React component
Gatsby primarily makes use of GraphQL to load data into components however there are other data sources that can be used as well as custom plugins that can be used or custom written for this purpose
Common Site Metadata
The place for common site data, such as the site title is the gatsby-config.js
file, we can put this in the siteMetadata
object like so:
gatsby-config.js
1module.exports = {2 siteMetadata: {3 title: 'Site Title from Metadata'4 },5 ...6}
We can then query for the data by using the GraphQL query constant that we export on a Page Component which states the data
required for the page itself
index.js
1import React from 'react'2import { graphql } from 'gatsby'3import Container from '../components/container'4
5export default ({ data }) => (6 <Container>7 <h1>Title: {data.site.siteMetadata.title}</h1>8 </Container>9)10
11export const query = graphql`12 query {13 site {14 siteMetadata {15 title16 }17 }18 }19`
Other components can make use of the useStaticQuery
hook, we can import it from gatsby
Let’s add the a simple static query for the title
in the container
component
container.js
1import { useStaticQuery, Link, graphql } from 'gatsby'
We can then use this in our component
1import React from 'react'2import { graphql } from 'gatsby'3import containerStyles from './container.module.css'4import { useStaticQuery } from 'gatsby'5
6export default ({ children }) => {7 const data = useStaticQuery(8 graphql`9 query {10 site {11 siteMetadata {12 title13 }14 }15 }16 `17 )18
19 return (20 <div className={containerStyles.container}>21 <p>{data.site.siteMetadata.title}</p>22 {children}23 </div>24 )25}
Source Plugins
Source plugins are how we pull data into our site, Gatsby comes with a tool called GraphiQL
which can be accessed at http://localhost:8000/___graphql
when the development server is running
We can write a query to get the title
using the GraphiQL
UI:
1query TitleQuery {2 site {3 siteMetadata {4 title5 }6 }7}
Filesystem Plugin
We can access data from the File System using the gatsby-source-filesystem
1yarn add gatsby-source-filesystem
And then in the gatsby-config.js
file:
1...2 plugins: [3 {4 resolve: `gatsby-source-filesystem`,5 options: {6 name: `src`,7 path: `${__dirname}/src/`,8 },9 },10...
If we restart the dev server we should see the allFile
and file
in the GraphiQL interface
We can then query for some data from the file system and log it to the console:
1import React from 'react'2import { graphql } from 'gatsby'3import Container from '../components/container'4
5export default ({ data }) =>6 console.log(data) || (7 <Container>8 <h1>Title: {data.site.siteMetadata.title}</h1>9 </Container>10 )11
12export const query = graphql`13 query {14 __typename15 allFile {16 edges {17 node {18 relativePath19 prettySize20 extension21 birthTime(fromNow: true)22 }23 }24 }25 site(siteMetadata: { title: {} }) {26 siteMetadata {27 title28 }29 }30 }31`
We can then build a simple table with the data:
1export default ({ data }) =>2 console.log(data) || (3 <Container>4 <h1>Title: {data.site.siteMetadata.title}</h1>5 <table>6 <thead>7 <tr>8 <th>relativePath</th>9 <th>prettySize</th>10 <th>extension</th>11 <th>birthTime</th>12 </tr>13 </thead>14 <tbody>15 {data.allFile.edges.map(({ node }, index) => (16 <tr key={index}>17 <td>{node.relativePath}</td>18 <td>{node.prettySize}</td>19 <td>{node.extension}</td>20 <td>{node.birthTime}</td>21 </tr>22 ))}23 </tbody>24 </table>25 </Container>26 )
Transformers
Transformers are used by Gatsby to transform the data that is read in, we can use the following transformer to transform markdown
1yarn add gatsby-transformer-remark
gatsby-config.js
1...2plugins: [3 'gatsby-transformer-remark',4...
We can then use the remark
plugin combined with the GraphQL query to get markdown content from files in our application
1query AllMarkdownQuery {2 __typename3 allMarkdownRemark {4 edges {5 node {6 fileAbsolutePath7 frontmatter {8 title9 date10 }11 excerpt12 html13 }14 }15 }16}
In the above query the result will be the rendered html
node along with any metadata, for example in the file below:
src/pages/article-1.md
1---2title: 'Sweet Pandas Eating Sweets'3date: '2017-08-10'4---5
6Pandas are really sweet.7
8Here's a video of a panda eating sweets.9
10<iframe width="560" height="315" src="https://www.youtube.com/embed/4n0xNbfJLR8" frameborder="0" allowfullscreen></iframe>
We can then use the query from above to create a page that lists all the markdown content we have in the site:
src/pages/blog.js
1import React from 'react'2import { graphql } from 'gatsby'3import Container from '../components/container'4
5export default ({ data }) => {6 console.log(data)7 return (8 <Container>9 <div>10 <h1>Amazing Pandas Eating Things</h1>11 <h4>{data.allMarkdownRemark.totalCount} Posts</h4>12 {data.allMarkdownRemark.edges.map(({ node }) => (13 <div key={node.id}>14 <h3>15 {node.frontmatter.title} <span>— {node.frontmatter.date}</span>16 </h3>17 <p>{node.excerpt}</p>18 </div>19 ))}20 </div>21 </Container>22 )23}24
25export const query = graphql`26 query {27 allMarkdownRemark {28 totalCount29 edges {30 node {31 id32 frontmatter {33 title34 date(formatString: "DD MMMM, YYYY")35 }36 excerpt37 }38 }39 }40 }41`
Create Pages Programatically
Using Gatsby we can create pages using the data output from a query
Generate Page Slugs
We can make use of the onCreateNode
and createPages
API’s that Gatsby exposes. To implement an API we need to export the function in the gatsby-node.js
file
The onCreateNode
function is run every time a new node is created or updated
We can add the following into the gatsby-node.js
file and can see each node that has been created
gatsby-node.js
1exports.onCreateNode = ({ node }) => {2 console.log(node.internal.type)3}
We can then check when a node is the MarkdownRemark
and use the gatsby-source-filesystem
plugin to generate a slug for the file
1const { createFilePath } = require(`gatsby-source-filesystem`)2
3exports.onCreateNode = ({ node, getNode }) => {4 if (node.internal.type === `MarkdownRemark`) {5 slug = createFilePath({ node, getNode, basePath: `pages` })6 console.log(slug)7 }8}
Using the above, we can update a node with the createNodeField
function which is part of the actions
object that’s passed into the onCreateNode
field
1const { createFilePath } = require(`gatsby-source-filesystem`)2
3exports.onCreateNode = ({ node, getNode, actions }) => {4 const { createNodeField } = actions5 if (node.internal.type === `MarkdownRemark`) {6 const slug = createFilePath({ node, getNode, basePath: `pages` })7 createNodeField({8 node,9 name: `slug`,10 value: slug,11 })12 }13}
We can then run the following query in the GraphiQL editor to see the slugs that were generated
1query {2 allMarkdownRemark {3 edges {4 node {5 fields {6 slug7 }8 }9 }10 }11}
Gatsby uses the createPages
API from plugins to create pages, we can additionally export the createPages
function from our gatsby-node.js
file. To create a page programatically we need to:
- Query the data
gatsby-node.js
1...2exports.createPages = async ({ graphql, actions }) => {3 // **Note:** The graphql function call returns a Promise4 // see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise for more info5 const result = await graphql(`6 query {7 allMarkdownRemark {8 edges {9 node {10 fields {11 slug12 }13 }14 }15 }16 }17 `)18 console.log(JSON.stringify(result, null, 4))19}
- Map the query resilt to a page
We can first update the createPages
function to set the slug route to resolve to a specific component, in this case the src/templates/blog-post.js
1const path = require(`path`)2
3...4
5exports.createPages = async ({ graphql, actions }) => {6 const { createPage } = actions7 const result = await graphql(`8 query {9 allMarkdownRemark {10 edges {11 node {12 fields {13 slug14 }15 }16 }17 }18 }19 `)20 result.data.allMarkdownRemark.edges.forEach(({ node }) => {21 createPage({22 path: node.fields.slug,23 component: path.resolve(`./src/templates/blog-post.js`),24 context: {25 // Data passed to context is available26 // in page queries as GraphQL variables.27 slug: node.fields.slug,28 },29 })30 })31}
We can then create the src/templates/blog-post.js
file to render the new data:
1import React from 'react'2import { graphql } from 'gatsby'3import Container from '../components/container'4
5export default ({ data }) => {6 const post = data.markdownRemark7 return (8 <Container>9 <div>10 <h1>{post.frontmatter.title}</h1>11 <div dangerouslySetInnerHTML={{ __html: post.html }} />12 </div>13 </Container>14 )15}16
17export const query = graphql`18 query ($slug: String!) {19 markdownRemark(fields: { slug: { eq: $slug } }) {20 html21 frontmatter {22 title23 }24 }25 }26`
You should be able to view any created pages by navigating to a random route on your site which should open the development server’s 404 page which has a listing of the available pages
We can then also update the blog.js
file to query for the slug and create a Link
to the new page based on the slug
blog.js
1import React from 'react'2import { graphql, Link } from 'gatsby'3import Container from '../components/container'4
5export default ({ data }) => {6 console.log(data)7 return (8 <Container>9 <div>10 <h1>Amazing Pandas Eating Things</h1>11 <h4>{data.allMarkdownRemark.totalCount} Posts</h4>12 {data.allMarkdownRemark.edges.map(({ node }) => (13 <div key={node.id}>14 <h3>15 {node.frontmatter.title} <span>— {node.frontmatter.date}</span>16 </h3>17 <p>{node.excerpt}</p>18 <Link to={node.fields.slug}>Read More</Link>19 </div>20 ))}21 </div>22 </Container>23 )24}25
26export const query = graphql`27 query {28 allMarkdownRemark {29 totalCount30 edges {31 node {32 id33 frontmatter {34 title35 date(formatString: "DD MMMM, YYYY")36 }37 fields {38 slug39 }40 excerpt41 }42 }43 }44 }45`