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-cliOr if using yarn:
1yarn global add gatsby-cliTo view the Gatsby help menu once installed:
1gatsby --helpCreate New Site
To create a new Gatsby.js site run:
1gatsby new gatsby-hello-world https://github.com/gatsbyjs/gatsby-starter-hello-worldWhere 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 developOr npm:
1npm run developOr
1yarn developYou 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.icoIn 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 buildOr
1npm run buildOr
1yarn buildYou 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
typographyplugin with:
1npm install --save gatsby-plugin-typography react-typography typography typography-theme-fairy-gatesOr
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 typographyIf 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-filesystemAnd 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-remarkgatsby-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`