Last modified on 01 Oct 2021.

Jekyll is great but for a note-taking site like this site, it’s slow. I decide to learn about Gatsby to make my note site more faster!

Misc

👉 If you wanna install some additional plugins/components, remember to add --save at the end of the installation line. This step is to add to package.json.

# for example
npm i gatsby-plugin-react-head react-head --save

When you install something new, don’t forget to cd to the current working directory, e.g. /gatsby-site.

👉 React / Gatsby use JSX syntax. It’s an XML/HTML-like syntax used by React that extends ECMAScript so that XML/HTML-like text can co-exist with JavaScript/React code.

👉 Read this to understand the differences between Class Component and Functional Component (a.k.a. stateless). Below are 2 examples which give the same result.

// Class Component
class MyComponentClass extends React.Component {
  render() {
    return <div>{this.props.name}</div>;
  }
}
// Functional Component
const MyStatelessComponent = props => <div>{props.name}</div>;
// without JSX
const MyStatelessComponent = props => React.createElement('div', null, props.name);
  • Functional Component (stateless component): just a plain javascript function which takes props as an argument and returns a react element. You can’t reach this.state inside it.
  • Component class: has a state, lifecycle hooks and it is a javascript class.

The rule would be: if your component needs some data which cannot be passed as a prop use class component to get that data. If you need to keep UI state in your component(expandable blocks) so it’s a good place to keep that info in a components state.

Installation on localhost

Install npm and NodeJS (with npm). Check the current version: npm -v (for npm) and node -v (for nodejs). After that, you can install Gatsby,

# install
npm install --global gatsby-cli
# check version
gatsby -v
# update
npm i -g gatsby-cli
  • Create a new folder by: mkdir ~/.npm-global
  • Open ~/.profile
  • Add following lines to this file

    npm config set prefix '~/.npm-global'
    export PATH=~/.npm-global/bin:$PATH
    
  • Save the file and then run (if you don’t restart the computer, do the same below work for new tab of terminal): source ~/.profile.

Install new site with

gatsby new gatsby-site # create a new site with name 'gatsby-site'
cd gatsby-site
gatsby develop # run the site at http://localhost:8000

You can view GraphiQL, an in-browser IDE, to explore your site’s data and schema,

http://localhost:8000/___graphql

Suppose that in ListAccount.js,

export default props => (
  <StaticQuery
    query={graphql`
      query AccountItemsQuery {
        allAccountItemsJson{
          edges{
            node{
              name
              icon{
                childImageSharp {
                  fixed(width: 150, height: 150) {
                    ...GatsbyImageSharpFixed_tracedSVG
                  }
                }
              }
              url
              opacity
              title
            }
          }
        }
      }
    `}
    render={data => <ListSocial accounts={data} {...props} />}
  />
)

In http://localhost:8000/___graphql,

query AccountItemsQuery {
  allAccountItemsJson {
    edges {
      node {
        name
        icon {
          childImageSharp {
            fixed(width: 150, height: 150) {
              tracedSVG
            }
          }
        }
        url
        opacity
        title
      }
    }
  }
}

and then click on Run icon to see the result!

Errors?

👉 Requires…

# [email protected] requires a peer of [email protected]
npm i [email protected] --save

# [email protected] requires a peer of typescript@>=2.8.0
npm i typescript --save

👉 Cannot read property…

# TypeError: Cannot read property 'fileName' of undefined

Above error comes from inserting images using query. To overcome this, we have to use StaticQuery which is introduced in Gatsby v2 (I don’t know why it works!?) 👉 The reason is that the (old) page query can only be added to page components (in my try, I add in Header.js component). StaticQuery can be used as a replacement of page query, it can be added to any component.[ref]

👉 Fail to build on Netlify Build script returned non-zero exit code: 127,

  • Delete package-lock.json, don’t include it and node_modules on git.
  • Remove either package.json or yarn.lock on Github (remove yarn).
  • "version": "0.1", is wrong, changing to "1.0.1" is OK.
  • Try to debug with netlify on localhost.
    • After installing, cd to gatsby-site and then run netlify dev.
    • Read more and more.

👉 Fail to build on Netlify Can't resolve '../components/Header' in '/opt/build/repo/src/components' for examples. 👉 The problem comes from the original name of file Header.js is header.js. I renamed it to Header.js but it’s still actually header.js (check the Github Desktop to see). You can change is to HeaderNew.js to fix the problem!

👉 If you wanna use adjacent react components, you have to put them inside <>..</> (React fragment) like below example,

return (
    <>
      <Navigation></Navigation>
      <Header type={headerType} />
      <span>Thi</span>
    </>
  )

This allows you to return multiple child components without appending additional nodes to the DOM.

👉 Warning: Each child in a list should have a unique "key" prop. You have to make sure that each child of a list in react component has a unique key. For example

// error
{links.map(link => (
  <>
    <span key={link.name}> Thi </span>
    <Link key={link.name}> {link.name} </Link>
  </>
))}
// but this
{links.map(link => (
  <span key={link.name}>
    <span> Thi </span>
    <Link> {link.name} </Link>
  </>
))}

Components from Gatsby

👉 Link (replaces <a> tag for internal links). For external links, use <a></a> as usual.

import { Link } from 'gatsby'
<Link to="/">Text<Link/> // only used for internal links

You cannot use target='_blank' with <Link> because whenever you use internal links, they are always in the same window!

👉 Use className instead of class=. E.g. className = "abc" or className = "abc xyz".

👉 Inline CSS, <div style={{ color: "#ffff", paddingTop: "10px" }}></div>.

👉 Date in Gatsby: {new Date().getFullYear()} or using moment.js.

Gatsby structure

Understand props

When React sees an element representing a user-defined component, it passes JSX attributes to this component as a single object. We call this object “props” (properties).[ref]

Components

A page is basically,

import React from "react"
function AboutPage(props) {
  return (
    <div className="about-container">
      <p>About me.</p>
    </div>
  )
}

export default AboutPage
import React from "react"
export default (props) => {
  return (
    // ...
  )
}

// or
const AboutPage = (props) => (
  // ...
)
export default AboutPage

Apply Bootstrap

Learn from Starter theme

💡 You can install a Gatsby Bootstrap Starter,

gatsby new gatstrap https://github.com/jaxx2104/gatsby-starter-bootstrap

Using plugins

❓ What if you wanna start from the beginning? 👉 Install react-bootstrap and bootstrap,

npm install react-bootstrap bootstrap --save
# --save to save to package.json

Import below line in /gatsby-browser.js,

import 'bootstrap/dist/css/bootstrap.min.css';

Using CDN from Bootstrap

❓ If you wanna use CDN? 👉 Put below line in <head> by using React Helmet,

<Helmet>
  <link rel="stylesheet" href=".../bootstrap.min.css" integrity="..." crossOrigin="anonymous" />
  <script src=".../jquery-3.4.1.slim.min.js" integrity="..." crossOrigin="anonymous"></script>
  <script src=".../popper.min.js" integrity="..." crossOrigin="anonymous"></script>
  <script src=".../bootstrap.min.js" integrity="..." crossOrigin="anonymous"></script>
</Helmet>

You can put above codes directly in your layout.js or index.js. All the <link> and <script> tags will be included in the <head>. For example in the file src/pages/index.js,

// src/pages/index.js
import Layout from "../layouts/layout"
import Helmet from "react-helmet"

const IndexPage = () => (
  <Layout>
    <Helmet>
      // the codes
    </Helmet>
    <h1>Hi people</h1>
    <p>Welcome to your new Gatsby site.</p>
    // other codes...
  </Layout>
)

export default IndexPage

❓ If you wanna put bootstrap.js in the footer? 👉 You can read this tutorial to add <script> / <link> tags in <head>, start or end of <body> tag. For example, in order to put above scripts/links before </body>, paste below code in /gatsby-ssr.js,

// file /gatsby-ssr.js
const React = require("react")
exports.onRenderBody = ({
  setHeadComponents,
  setPostBodyComponents,
}) => {
  setHeadComponents([
    <link key='bootstrap-css' rel="stylesheet" href=".../bootstrap.min.css" integrity="..." crossOrigin="anonymous" />,
  ])
  setPostBodyComponents([
    <script key="jquery-3-4-1" type="text/javascript" src=".../jquery-3.4.1.slim.min.js" integrity="..." crossOrigin="anonymous" />,
    <script key="proper-1-16" type="text/javascript" src=".../popper.min.js" integrity="..." crossOrigin="anonymous" />,
    <script key="bootstrap-js" type="text/javascript" src=".../bootstrap.min.js" integrity="..." crossOrigin="anonymous" />,
  ])
}

Remember to restart gatsby (Ctrl + C to stop and run gatsby develop to start again).

Using sass

// in /scr/pages/index.js
import Layout from "../layouts/layout"
// in /scr/layouts/layout.js
import "../styles/main.scss"
// in /scr/styles/main.scss
@import "layout";
// in /scr/styles/_layout.scss
// scss codes

Differences between layouts and templates

There are 2 separated folders /src/layouts and /src/templates.

  • layouts: components are for everything shared across pages e.g. headers, footers, sidebars, etc.
  • templates: components are for page types e.g. blog posts, documentation pages, etc.

Design base layout

What I need in the base layout:

  • A fixed navigation bar on top.
  • A fixed footer on bottom.
  • A flexible header.
  • A body wraper.

Design post / page templates

Their differences are just the width of the container.

Different <Header> for different page types

// in src/components/Header.js
import React, { Component } from 'react'

export default class Header extends Component {
  render() {
    const headerType = this.props.type
    switch (headerType) {
      case 'index':
        return (
          <>
            <header className="idx-header header">
              ...
            </header>
          </>
        )
      default:
        return (
          <>
            <header className="header">
              ...
            </header>
          </>
        )
    }
  }
}
// in src/layouts/base.js
import Header from "../components/Header"
const Layout = ({ children, headerType='page' }) => {
  return (
    <>
      <Header type='index' />
      {children}
    </>
  )
}
export default Layout
// in src/pages/index.js
import Layout from "../layouts/base"
const IndexPage = () => (
  <Layout headerType='index'>
    ...
  </Layout>
)
export default IndexPage

Add Navigation bar

Using react-bootstrap, create a file src/components/Navigation.js whose content is,

import React from 'react'
import {Navbar, Nav, NavDropdown, Form, FormControl, Button} from 'react-bootstrap'

export default (props) => (
  // the codes from https://react-bootstrap.netlify.com/components/navbar/#navbars
)

Then, in /src/Header.js,

import Navigation from '../components/Navigation'

const Header = () => (
  <header ..>
    <Navigation></Navigation>
    // other codes
  </header>
)

If you get stuck, check this video.

Using Font Awesome

Install (the free things) (if you have a pro license, read this) or this,

npm i --save @fortawesome/fontawesome-svg-core @fortawesome/react-fontawesome @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons @fortawesome/free-brands-svg-icons

To import everything in one place instead of importing each icon into each separate file, we’ll create a Font Awesome library. Creat src/components/fontawesome.js

// import the library
import { library } from '@fortawesome/fontawesome-svg-core';

// import your icons
import { faHome, faFire, faEdit,  } from '@fortawesome/free-solid-svg-icons';

library.add(
  faHome, faFire, faEdit,
);

Note that, an icon fas fa-money-bill will have name faMoneyBill from free-solid-svg-icons. In the case you wanna import an entire package,

import { library } from '@fortawesome/fontawesome-svg-core';
import { fab } from '@fortawesome/free-brands-svg-icons';

library.add(fab);

In src/pages/index.js (for example),

import '../components/fontawesome'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'

<FontAwesomeIcon icon={'home'} /> // for 'faHome' or 'fas fa-home'
<FontAwesomeIcon icon={['fab', 'github']} /> // for 'faGithub' or `fab fa-github`

💡 Yes! fortawesome is correct!!!

💡 If you have a problem in that the icon is firstly flashing big and then smaller, you need to set the configuration autoAddCss to false,[ref]

import { config  } from '@fortawesome/fontawesome-svg-core'
import "@fortawesome/fontawesome-svg-core/styles.css"
config.autoAddCss = false

Google Fonts

Using typeface.js (search font in npmjs),

# install
npm install --save typeface-open-sans
# in gatsby-browser.js
require('typeface-open-sans');

Rebuild to see the result!

Below is the old method (it didn’t work well, it doesn’t contain font-weight 600 for Open Sans without reason).

npm install --save gatsby-plugin-prefetch-google-fonts
// in /gatsby-config.js
module.exports = {
 plugins: [
    {
      resolve: `gatsby-plugin-prefetch-google-fonts`,
      options: {
        fonts: [
          {
            family: `Roboto Mono`,
            variants: [`400`, `700`]
          },
          {
            family: `Roboto`,
            subsets: [`latin`]
          },
        ],
      },
    }
  ]
}

Insert images / photos

Read this note.

Adding markdown posts

Posts (.md files) are stored in /content/posts/. Install gatsby-transformer-remark,

npm install --save gatsby-transformer-remark

And add the following to gatsby-config.js (),

plugins: [
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      name: `posts`,
      path: `${__dirname}/content/posts`,
    },
  },
  `gatsby-transformer-remark`,
  // there may be already others like this
  {
    resolve: `gatsby-source-filesystem`,
    options: {
      name: `images`,
      path: `${__dirname}/src/images`,
    },
  },
]

Create a file called post-1.md in content/posts/,

---
path: "/first-post"
date: "2019-05-04"
title: "My first blog post"
---

…read this and this example for more…

Display site / post info on browser tab

import Layout from "../layouts/base"
import Helmet from 'react-helmet'
const IndexPage = () => (
  <Layout>
    <Helmet title={`Thi | I failed my way to success`} />
  </Layout>
)

Create page template and markdown file

Read this note.

Render html tag in a string

Instead of <p dangerouslySetInnerHTML={{ headerIntro }} />, you can use <p dangerouslySetInnerHTML={{__html: headerIntro }} />. If there is a html tag in headerIntro, e.g. "<i>Hello</i>" will be rendered as Hello.

JSX in Markdown

  • Download and use MDX plugin.
  • We have to put all mdx files in /src/pages. The mdx files will be renders automatically! That’s why we need to indicate defaultLayouts in /gatsby-config.js[ref] . I have tried to render mdx in /content/pages/ but it didn’t work!
  • For an example of using graphql in mdx file, check /src/pages/about.mdx.
  • For a specific page, one can use props.pageContext.frontmatter.title to take the title of that page.
  • For writing pages, read this.

Create page template and markdown file

Suppose that you wanna create a page /about taking content from file /content/pages/about.md and it applies template /src/templates/page.js. All you need to do is following this post.

  1. First, add to /gatsby-config.
  2. Create /src/templates/page.js,
  3. Create markdown file /content/pages/about.md.
  4. Modify /gatsby-node.js to tell gatsby to create a page /about from about.md using template page.js.

References