Skip to content

Tips & Tricks for Gatsby

02/03/20199 min readCategory: Tutorial

For some time now I've been trying to publish quick tips about Gatsby on Twitter, because the community loves such short, useful tips. Accordingly, I had already created a Twitter moment back then - but why only content from me? On January 6th of this year I also called the community to share their quick tips (with great success). As promised, this blogpost is supposed to be a collection of these (and other) tips. You can also tag me on Twitter if you want to add your tip to the moment!

Some of the tips are also accompanied by Codesandbox examples.

Tweet's contents

Most tips on Twitter are code examples embedded in an image. For a blog post it wouldn't make much sense to display images instead of copy-able code - therefore the tweets are not embedded here, but the authors are quoted differently. If you are the author of one of these tweets and disagree, please contact me. Thanks a lot!

#Date of last Build

Gives you the date for when you last used gatsby build . In the case of e.g. Netlify that would be the last time you deployed your site (hence buildTime).
Codesandbox

import React from 'react'
import { useStaticQuery, graphql } from 'gatsby'

const IndexPage = () => {
  const data = useStaticQuery(query)

  return (
    <>
      <p>This site was last built on:</p>
      <p>{data.site.buildTime}</p>
    </>
  )
}

export default IndexPage

const query = graphql`
  query Info {
    site {
      buildTime(formatString: "DD/MM/YYYY")
    }
  }
`

#Date of last modification

For documentations it's good to know when a document was last modified. You can get that information via the parent. Use the ___graphql endpoint to explore that information on your own project.
Codesandbox

query YourQuery {
  allMdx {
    edges {
      node {
        parent {
          ... on File {
            modifiedTime(formatString: "MM/DD/YYYY")
          }
        }
      }
    }
  }
}

#Same Source, different Queries

When you define two (or more) gatsby-source-filesystem entries in your config you can filter your GraphQL queries to only get the result of one source.
Codesandbox

gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `assets`,
        path: `${__dirname}/src/assets`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `logos`,
        path: `${__dirname}/src/logos`,
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
  ],
}

Now place one image file in the src/logos folder and two or more image files in the src/assets folder. This should show the difference between the file and allFile Query (of course you can put any number of files into one folder). You can address both folders individually, as shown in the following example:

src/pages/index.js
import React from 'react'
import { useStaticQuery, graphql } from 'gatsby'
import Img from 'gatsby-image'

const IndexPage = () => {
  const data = useStaticQuery(query)

  return (
    <>
      <p>First image (logo):</p>
      <Img
        style={{ width: '200px', marginBottom: '2rem' }}
        fluid={data.logo.childImageSharp.fluid}
      />
      <p>Assets images (two):</p>
      <div style={{ display: 'flex', flexWrap: 'wrap' }}>
        {data.assets.edges.map(img => (
          <Img
            style={{ width: '200px', marginBottom: '2rem' }}
            fluid={img.node.childImageSharp.fluid}
          />
        ))}
      </div>
    </>
  )
}

export default IndexPage

const query = graphql`
  query Images {
    logo: file(sourceInstanceName: { eq: "logos" }) {
      childImageSharp {
        fluid(maxWidth: 200, quality: 100) {
          ...GatsbyImageSharpFluid
        }
      }
    }
    assets: allFile(filter: { sourceInstanceName: { eq: "assets" } }) {
      edges {
        node {
          childImageSharp {
            fluid(maxWidth: 200, quality: 100) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    }
  }
`

#GraphQL Promise Wrapper

You can not only use async/await in gatsby-node.js but also write a little promise wrapper helper. As the graphql function doesn't throw its errors you manually have to check for result.errors and throw an error. Otherwise errors would get swallowed.
Codesandbox

const wrapper = promise =>
  promise.then(result => {
    if (result.errors) {
      throw result.errors
    }
    return result
  })

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const yourTemplate = require.resolve('./src/templates/yourTemplate.js')

  const result = await wrapper(
    graphql(`
      {
        ... your GraphQL query
      }
    `)
  )

  result.data.yourNode.edges.forEach(edge => {
      // createPage function
  })
}

Most of the times you want to show a previous/next post under your blog post to keep the visitor reading. For that you can use pageContext.
Codesandbox

gatsby-node.js
const wrapper = promise =>
  promise.then(result => {
    if (result.errors) {
      throw result.errors
    }
    return result
  })

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const bookTemplate = require.resolve('./src/templates/book.js')

  const result = await wrapper(
    graphql(`
      {
        allBooksYaml(sort: { fields: [title], order: ASC }) {
          edges {
            node {
              slug
              title
            }
          }
        }
      }
    `)
  )

  const books = result.data.allBooksYaml.edges

  books.forEach((edge, index) => {
    // Set the prev/next variable for every node so
    // that you can directly access slug & title

    const prev = index === 0 ? null : books[index - 1].node
    const next = index === books.length - 1 ? null : books[index + 1].node

    createPage({
      path: edge.node.slug,
      component: bookTemplate,
      context: {
        slug: edge.node.slug,
        prev,
        next,
      },
    })
  })
}
src/templates/book.js
import React from 'react'
import { graphql, Link } from 'gatsby'

import Layout from '../components/layout'

class BookTemplate extends React.Component {
  render() {
    const { booksYaml } = this.props.data
    // prev & next passed through from gatsby-node.js
    const { prev, next } = this.props.pageContext
    return (
      <Layout location={this.props.location}>
        <h2>{booksYaml.title}</h2>
        <div>
          {prev && (
            <div>
              <span>Previous</span>
              <Link to={prev.slug}>{prev.title}</Link>
            </div>
          )}
          {next && (
            <div>
              <span>Next</span>
              <Link to={next.slug}>{next.title}</Link>
            </div>
          )}
        </div>
      </Layout>
    )
  }
}

export default BookTemplate

export const pageQuery = graphql`
  query BookBySlug($slug: String!) {
    booksYaml(slug: { eq: $slug }) {
      title
      content
    }
  }
`

In contrast to the previous tip you can also show two (or more) random posts by changing gatsby-node.js.
Codesandbox

const _ = require('lodash')

const prevNext = (list, item) => {
  // Create a random selection of other posts (excluding the current post)
  const filterUnique = _.filter(
    list,
    input => input.node.slug !== item.node.slug
  )
  const sample = _.sampleSize(filterUnique, 2)

  return {
    left: sample[0].node,
    right: sample[1].node,
  }
}

const wrapper = promise =>
  promise.then(result => {
    if (result.errors) {
      throw result.errors
    }
    return result
  })

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const bookTemplate = require.resolve('./src/templates/book.js')

  const result = await wrapper(
    graphql(`
      {
        allBooksYaml {
          edges {
            node {
              slug
              title
            }
          }
        }
      }
    `)
  )

  const books = result.data.allBooksYaml.edges

  books.forEach(edge => {
    const { left, right } = prevNext(books, edge)

    createPage({
      path: edge.node.slug,
      component: bookTemplate,
      context: {
        slug: edge.node.slug,
        left,
        right,
      },
    })
  })
}

#Use TailwindCSS with CSS-in-JS Libraries

Tailwind is not a UI-Kit but a "utility-first" CSS Framework. With the help of Babel macros you can use Tailwind with any CSS-in-JS library (because Gatsby supports Babel macros out of the box).
Codesandbox

// This little guide assumes that you already have a CSS-in-JS library set up 
// in your Gatsby project. In this case you use "styled-components"

// Step 1: Install the needed npm packages

$ npm install --save tailwind.macro tailwindcss

// Step 2: Create a tailwind config file at the root

$ ./node_modules/.bin/tailwind init tailwind.js

// Step 3: Use the Babel Macro (tailwind.macro) in your styled component

import styled from 'styled-components'
import tw from 'tailwind.macro'

const Button = styled.button`
  ${tw`bg-blue hover:bg-blue-dark text-white font-bold py-2 px-4 rounded font-sans border-none`}
`

#Quick tips

Some shorter tips to loosen up the listing :)

  • Use Cypress for testing (Example: lekoarts.de) - Source
  • Create a helper that checks if typeof window !== 'undefined' and use it everytime you use browser APIs - Source
  • Optimize your pictures before you start using Gatsby! Gatsby processes images faster if you optimize your images beforehand, e.g. by reducing the image size. If, for example, you know from the beginning that your images should not be larger than 2000px: Reduce all pictures to this size.
  • If you have any questions: Visit the Gatsby Discord server! :) Other community members will be happy to help you
  • All .js/.jsx files inside the src/pages folder are automatically converted to pages by Gatsby. It is best to place those files somewhere else - or if there is no other way, you can add an underscore to them: _styled.js

#Fontawesome SSR

You can use Fontawesome Icons with SSR!
Original Tweet

// Wherever you're importing your styles, include:
import '@fortawesome/fontawesome-svg-core/styles.css'

// or if you're using something like styled-components
import { createGlobalStyle } from 'styled-components'
import { dom } from '@fortawesome/fontawesome-svg-core'

const GlobalStyle = createGlobalStyle`
  // global site styles

  ${dom.css()}
`

#Download Images from a CDN

If your images are hosted on a CDN and you want to use gatsby-image, you can download them using the createRemoteFileNode helper. Then add the image to the respective node.
Original Tweet

gatsby-node.js
const { createRemoteFileNode } = require('gatsby-source-filesystem')

exports.onCreateNode = async ({
  node,
  actions,
  store,
  cache,
  createNodeId,
}) => {
  const { createNodeField, createNode } = actions

  if (node.internal.type === 'YourNodeType') {
    const imageNode = await createRemoteFileNode({
      url: node.yourImage.href,
      store,
      cache,
      createNode,
      createNodeId,
    })

    if (imageNode) {
      createNodeField({
        node,
        name: `yourNewImage___NODE`,
        value: imageNode.id,
      })
    }
  }
}

#Use your smartphone

You can also visit the local development server on your phone!
Original Tweet

  1. gatsby develop -o -H $HOSTNAME -p 8000
  2. Visit $IP_OF_YOUR_PC:8000

#Gatsby + Apollo

Build-time and runtime GraphQL with Apollo
Original Tweet

gatsby-config.js
module.exports = {
  siteMetadata: {
    title: 'Gatsby With Apollo',
  },
  plugins: [
    {
      resolve: 'gatsby-source-graphql',
      options: {
        typeName: 'RMAPI',
        fieldName: 'rickAndMorty',
        url: 'https://rickandmortyapi-gql.now.sh/',
      },
    },
  ],
}
gatsby-browser.js & gatsby-ssr.js
import React from 'react'
import { ApolloProvider } from 'react-apollo'
import { client } from './src/apollo/client'

export const wrapRootElement = ({ element }) => (
  <ApolloProvider client={client}>{element}</ApolloProvider>
)
src/apollo/client.js
import ApolloClient from 'apollo-boost'
import fetch from 'isomorphic-fetch'

export const client = new ApolloClient({
  uri: 'https://nx9zvp49q7.lp.gql.zone/graphql',
  fetch,
})
src/pages/index.js
import React from 'react'
import { graphql } from 'gatsby'
import { Query } from 'react-apollo'
import gql from 'graphql-tag'

// This query is executed at build time by Gatsby.
export const GatsbyQuery = graphql`
  {
    rickAndMorty {
      character(id: 1) {
        name
        image
      }
    }
  }
`

// This query is executed at run time by Apollo.
const APOLLO_QUERY = gql`
  {
    dog(breed: "frise") {
      breed
      displayImage
    }
  }
`

export default ({
  data: {
    rickAndMorty: { character },
  },
}) => (
  <div style={{ textAlign: 'center', width: '600px', margin: '50px auto' }}>
    <h1>{character.name} With His Pupper</h1>
    <p>
      Rick & Morty API data loads at build time. Dog API data loads at run time.
    </p>
    <div>
      <img src={character.image} alt={character.name} style={{ width: 300 }} />

      <Query query={APOLLO_QUERY}>
        {({ data, loading, error }) => {
          if (loading) return <p>Loading pupper...</p>
          if (error) return <p>Error: ${error.message}</p>

          const { displayImage: src, breed } = data.dog
          return <img src={src} alt={breed} style={{ maxWidth: 300 }} />
        }}
      </Query>
    </div>
  </div>
)

#Use Gatsby without GraphQL

Did you know that Gatsby can also create pages without GraphQL? The following example loads data from the PokéAPI, creates a page with links to all Pokémon, its respective individual pages and pages for the abilities of each Pokémon.
Original Tweet / Codesandbox

gatsby-node.js
const axios = require('axios')

const get = endpoint => axios.get(`https://pokeapi.co/api/v2${endpoint}`)

const getPokemonData = names =>
  Promise.all(
    names.map(async name => {
      const { data: pokemon } = await get(`/pokemon/${name}`)
      const abilities = await Promise.all(
        pokemon.abilities.map(async ({ ability: { name: abilityName } }) => {
          const { data: ability } = await get(`/ability/${abilityName}`)

          return ability
        })
      )

      return { ...pokemon, abilities }
    })
  )

exports.createPages = async ({ actions: { createPage } }) => {
  const allPokemon = await getPokemonData(['pikachu', 'charizard', 'squirtle'])

  // Create a page that lists all Pokémon.
  createPage({
    path: `/`,
    component: require.resolve('./src/templates/all-pokemon.js'),
    context: { allPokemon },
  })

  // Create a page for each Pokémon.
  allPokemon.forEach(pokemon => {
    createPage({
      path: `/pokemon/${pokemon.name}/`,
      component: require.resolve('./src/templates/pokemon.js'),
      context: { pokemon },
    })

    // Create a page for each ability of the current Pokémon.
    pokemon.abilities.forEach(ability => {
      createPage({
        path: `/pokemon/${pokemon.name}/ability/${ability.name}/`,
        component: require.resolve('./src/templates/ability.js'),
        context: { pokemon, ability },
      })
    })
  })
}

#Open your browser automatically

Did you know Gatsby's CLI also has options? I like to add the following script to the scripts section to automatically open the browser:

"scripts": {
  "dev": "gatsby develop -o"
}

#Custom Hook with useStaticQuery

React Hooks are probably the favorite topic of all React developers at the moment... And there is also the useStaticQuery hook. Use it to build your own hook for the metadata!
Codesandbox

src/components/buildTime.js
import { useStaticQuery, graphql } from 'gatsby'

function useBuildTime() {
  const data = useStaticQuery(graphql`
    query Info {
      site {
        buildTime(formatString: "DD/MM/YYYY")
      }
    }
  `)

  return data.site.buildTime
}

export default useBuildTime
src/pages/index.js
import React from 'react'

import useBuildTime from '../components/buildTime'

const IndexPage = () => {
  const time = useBuildTime()

  return (
    <>
      <h1>
        Hooks{' '}
        <span aria-label="hook-emoji" role="img"></span>
      </h1>
      <p>Last build time was: {time}</p>
    </>
  )
}

export default IndexPage

#Use dotenv

By default, dotenv is automatically installed with Gatsby and allows you to store ENV variables in files and use them on different operating systems. Import dotenv into your gatsby-config.js file and you can use .env.development or .env files.

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`,
})

#GraphQL Playground

By setting an environment variable you can use Prisma Playground. You can for example use a script in your package.json:

"develop": "GATSBY_GRAPHQL_IDE=playground gatsby develop",

#Do you have any other tips?

Thanks for reading and I hope you enjoyed the tips! Share the article with your friends or write me on Twitter/via mail if you have any questions or even more tips! If you want to see some of the tips in action, you can visit my GitHub profile and watch my Gatsby starters :)

Sparked your interest? Read all posts in the category Tutorial

More posts