How to create a blog with Gatsby and upload it to a server

Gatsby is a web framework based on React.js and GraphQL. It is most commonly used for creating and optimizing static sites. Unlike other static sites generators like Jekyll or Hugo, sites created with Gatsby can serve as PWA's (Progressive Web Apps) using one source of code for all operating systems and browsers and always stay optimized to the latest standards.

In this tutorial, we will create a simple Gatsby blog that generates posts from Markdown files and then use Buddy CI/CD to automate the build and delivery process.

Prerequisites

To begin, all you need is Node and npm installed. This tutorial was written with Node in version 12.16.2, but any LTS in the current version should work just fine. You'll also need a code editor, for example PhpStorm or Visual Studio Code.

Hint
The repository that we used in this guide is available on our GitHub profile: github.com/buddy-works/gatsby-blog
Hint
Actions used in this guide: Gatsby CLI Netlify

Project Setup

Let's begin with the basics: installing the Gatsby CLI that will allow us to create a base project. Fire up your terminal and enter the following:

bash
npm install -g gatsby-cli $

With Gatsby installed, it's time to create a new project:

bash
gatsby new guide-blog $

The last step is testing if everything is working properly:

bash
cd guide-blog npm run develop $$

With all of this out of the way, you should be able to see a template page under localhost:8000:

Image loading...Starting website

Our project is now running in developer mode, which means that any change to the source code will be immediately visible on the page without the need of reloading the site manually. Before we proceed, however, we will install a bootstrap that will help us build some neat UI so that we don't have to code it in CSS.

bash
npm install react-bootstrap bootstrap $

Time to introduce some human touch to our creation.

Style customization and post display

Right now, your project should contain the following:

Image loading...Project contents

We'll be mainly focusing on three things:

  • src/components – that's where we will create our components and style them
  • src/pages – we'll be keeping our pages here; the file name translates directly to the URL
  • gatsby-config.js – this file allows us to manage the website's metadata and plugins

Header modification

Let's begin with modifying the header of our site so that our images will dynamically change on every access to the article. To do this, we need to do changes in two files. The first one is src/components/layout.js:

js
import React from "react" import 'bootstrap/dist/css/bootstrap.min.css'; import PropTypes from "prop-types" import { useStaticQuery, graphql } from "gatsby" import Header from "./header" import "./layout.css" const Layout = ({ children, imageUrl }) => { const data = useStaticQuery(graphql` query SiteTitleQuery { site { siteMetadata { title } } } `) return ( <> <Header siteTitle={data.site.siteMetadata.title} imageUrl={imageUrl} /> <div style={{ margin: `0 auto`, maxWidth: 960, padding: `0 1.0875rem 1.45rem` }} > <main>{children}</main> <footer> © {new Date().getFullYear()}, Built with {` `} <a href="https://www.gatsbyjs.org">Gatsby</a> </footer> </div> </> ) } Layout.propTypes = { children: PropTypes.node.isRequired } export default Layout

The second is src/components/header.js:

js
import PropTypes from "prop-types" import React from "react" const Header = ({ siteTitle, imageUrl }) => ( <header> <div style={{ height: "20rem", backgroundImage: `linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url(${imageUrl ? imageUrl : "https://images.unsplash.com/photo-1432821596592-e2c18b78144f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1950&q=80"})`, backgroundSize: "cover", backgroundPosition: "center" }} className="d-flex justify-content-center align-items-center" > <h1 className="text-white">{siteTitle}</h1> </div> </header> ) Header.propTypes = { siteTitle: PropTypes.string } Header.defaultProps = { siteTitle: `` } export default Header

Adding Markdown parsing

The blog we are creating will use Markdown files to generate posts. To do so, we have to install two plugins:

  • gatsby-source-filesystem – this plugin will let you use files from your local filesystem
  • gatsby-transformer-remark – this plugin will let you parse Markdown

The install command is:

bash
npm install --save gatsby-source-filesystem gatsby-transformer-remark $

Apart from the package installation, it is necessary to modify the gatsby-config.js to turn on Markdown-to-HTML parsing and define where our Markdown files will be stored. To do so, add the following code to the plugins board.

js
{ resolve: `gatsby-source-filesystem`, options: { path: `${__dirname}/src/articles`, name: `articles` } }, `gatsby-transformer-remark`,
Danger
Whenever you make changes to a config file, make sure to restart your console and run npm run develop to avoid errors.

The entire file should look something like this:

js
module.exports = { siteMetadata: { title: `Simple blog template`, description: `Blog Template made from scratch`, author: `@gatsbyjs` }, plugins: [ `gatsby-plugin-react-helmet`, { resolve: `gatsby-source-filesystem`, options: { name: `images`, path: `${__dirname}/src/images` } }, { resolve: `gatsby-source-filesystem`, options: { path: `${__dirname}/src/articles`, name: `articles` } }, `gatsby-transformer-remark`, `gatsby-transformer-sharp`, `gatsby-plugin-sharp`, { resolve: `gatsby-plugin-manifest`, options: { name: `gatsby-starter-default`, short_name: `starter`, start_url: `/`, background_color: `#663399`, theme_color: `#663399`, display: `minimal-ui`, icon: `src/images/gatsby-icon.png` // This path is relative to the root of the site. } } ] }

Testing display

Now, it's time to see if it works. Let's add two "Lorem Ipsum" articles. Create an articles directory in /src/ and add two md files that will serve as posts on our site:

  • src/articles/first-article.md

```md

path: "/first-article" date: "2020-04-29" title: "My first blog post" imageUrl: "https://images.unsplash.com/photo-1587058571836-8562bb05e548?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1335&q=80" ---

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi non maximus mi. Nunc vitae aliquam elit, eget rutrum urna. Vivamus ac risus nunc. Phasellus eleifend, mi vitae venenatis volutpat, elit justo sagittis ligula, bibendum aliquam ante odio ac leo. Phasellus rutrum nibh ultricies maximus ullamcorper. Maecenas hendrerit odio in velit facilisis blandit. Morbi non vestibulum massa. Pellentesque metus libero, accumsan vitae enim eget, vestibulum suscipit arcu. Sed egestas neque a quam sodales elementum. Quisque consequat sem mi, eu auctor leo dapibus et.

Vivamus elementum vel ligula sed vehicula. Ut tempus quam a ligula sodales, ac aliquam justo aliquam. Maecenas condimentum lorem arcu, at vulputate metus aliquet eget. Aliquam a vulputate nibh, ultrices viverra ante. Nulla eget ex congue, fermentum ipsum et, euismod neque. Etiam vestibulum fermentum justo, at imperdiet ligula rutrum eget. Phasellus eu rhoncus nulla.

default
- `src/articles/second-article.md`

md

path: "/second-article" date: "2020-04-29" title: "My second blog post" imageUrl: "https://images.unsplash.com/photo-1447014421976-7fec21d26d86?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" ---

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi non maximus mi. Nunc vitae aliquam elit, eget rutrum urna. Vivamus ac risus nunc. Phasellus eleifend, mi vitae venenatis volutpat, elit justo sagittis ligula, bibendum aliquam ante odio ac leo. Phasellus rutrum nibh ultricies maximus ullamcorper. Maecenas hendrerit odio in velit facilisis blandit. Morbi non vestibulum massa. Pellentesque metus libero, accumsan vitae enim eget, vestibulum suscipit arcu. Sed egestas neque a quam sodales elementum. Quisque consequat sem mi, eu auctor leo dapibus et.

Vivamus elementum vel ligula sed vehicula. Ut tempus quam a ligula sodales, ac aliquam justo aliquam. Maecenas condimentum lorem arcu, at vulputate metus aliquet eget. Aliquam a vulputate nibh, ultrices viverra ante. Nulla eget ex congue, fermentum ipsum et, euismod neque. Etiam vestibulum fermentum justo, at imperdiet ligula rutrum eget. Phasellus eu rhoncus nulla.

default
<Hint type="tip"> As you can see in the examples above, we have provided the metadata (path, date, title and image URL) in the very beginning. Of course, there is other information you can contain in the metadata, such as author, path to the thumbnail, or the last update. You are completely free to add any type of info you want – **just remember to indicate the data in the GraphQL query**. </Hint> With the example post contents up, it's time to add a template that will be used by our entries. Create the `src/templates/blogTemplate.js` file and paste the following code:

js import React from "react" import { graphql, Link } from "gatsby" import Layout from "../components/layout"

export default function Template({ data }) { const { markdownRemark } = data const { frontmatter, html } = markdownRemark

return (

default
<Layout imageUrl={frontmatter.imageUrl}> <div> <Link to="/">back</Link> <h1>{frontmatter.title}</h1> <h2>{frontmatter.date}</h2> <div dangerouslySetInnerHTML={{ __html: html }} /> </div> </Layout>

) }

export const pageQuery = graphql` query($path: String!) {

default
markdownRemark(frontmatter: { path: { eq: $path } }) { html frontmatter { date(formatString: "MMMM DD, YYYY") path title imageUrl } }

} `

default
In order for our GraphQL to execute, we have to support the <u>createPages</u> functions that will resolve the query and create the page. To achieve that, we have to modify the `gatsby-node.js` file:

js const path = require(path)

exports.createPages = async ({ actions, graphql, reporter }) => { const { createPage } = actions const blogPostTemplate = path.resolve(src/templates/blogTemplate.js) const result = await graphql(`

default
{ allMarkdownRemark( sort: { order: DESC, fields: [frontmatter___date] } limit: 1000 ) { edges { node { frontmatter { path } } } } }

`)

// Handle errors

if (result.errors) {

default
reporter.panicOnBuild(`Error while running GraphQL query.`) return

}

result.data.allMarkdownRemark.edges.forEach(({ node }) => {

default
createPage({ path: node.frontmatter.path, component: blogPostTemplate, context: {} })

}) }

default
### Creating a post list At this stage, the hardest part is behind us. All that's left is creating a post list that will link to the corresponding articles. In order to see our articles listed in a chronological order, edit the `src/pages/index.js` file as follows:

js import React from "react" import Layout from "../components/layout" import SEO from "../components/seo" import { graphql, Link } from "gatsby" import Card from "react-bootstrap/Card" import Button from "react-bootstrap/Button"

const IndexPage = ({ data: { allMarkdownRemark: { edges } } }) => { return (

default
<Layout> <SEO title="Home" /> <main className="d-flex flex-column align-items-center"> {edges.map(({ node }, i) => ( <div key={i} className="my-5"> <Card style={{ width: '40rem' }}> <Card.Img variant="top" src={node.frontmatter.imageUrl} /> <Card.Body> <Card.Title>{node.frontmatter.title} <small className="text-muted ">{node.frontmatter.date}</small></Card.Title> <Card.Text> {node.excerpt} </Card.Text> <Link to={node.frontmatter.path} className="text-white"> <Button variant="primary">Read</Button> </Link> </Card.Body> </Card> </div> ))} </main> </Layout>

) }

export const pageQuery = graphql` query {

default
allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) { edges { node { excerpt(pruneLength: 250) frontmatter { date(formatString: "MMMM DD, YYYY") path title imageUrl } } } }

} `

export default IndexPage

default
If you did everything correctly, you should see the list of your articles on the main page: ![Main page](/guides/images/gatsby/gatsby-3.png 640x695) Every consecutive `.md` file added to `src/articles` will be visible on the main page and will link to the article: ![Article](/guides/images/gatsby/gatsby-4.png 640x495) ## Deployment Our blog is almost ready. Now it's time to deploy it to the server and make it available to the public. For the purpose of this guide, we'll use Netlify as the hosting service, and GitHub as the source provider. The deployment process will be handled by Buddy. ### Configuring hosting service Let's begin by configuring our server. [Log in to Netlify](https://www.netlify.com) and click "New site from Git". Select your repository and add your site: ![Adding new website on Netlify](/guides/images/gatsby/gatsby-5.png 640x328) Since we want Buddy to handle the deployments, we'll need to turn off the setting with Netlify as the default publishing service. To do so, go to the <u>Deploys</u> tab and click `Stop auto publishing`: ![Netlify deployment settings](/guides/images/gatsby/gatsby-6.png 640x275) ### Configuring pipeline Now, go to Buddy and add the repository with the blog as a new project: ![Adding new project to Buddy](/guides/images/gatsby/gatsby-7.png 640x339) Buddy will synchronize with the selected repository and suggest creating a new pipeline: ![Post-synchronization screen](/guides/images/gatsby/gatsby-8.png 640x386) <Hint type="info"> A pipeline is a set of actions performed on the repository code, such as [builds or tests](/docs/pipelines/builds-and-testing). </Hint> We will now create a pipeline that will automatically generate the website and deploy it to Netlify on every push to the master branch. Click the button to enter the pipeline configuration and set the options accordingly: ![New pipeline configuration](/guides/images/gatsby/gatsby-9.png 640x518) The next step is adding the build and deployment actions. Basing on the repository contents, Buddy will automatically suggest the Gatsby CLI action. Click it to open its configuration details: ![Adding new action](/guides/images/gatsby/gatsby-10.png 640x395) Before building the site we need to install the packages listed in `package.json`. To do this, add `npm install` before the build command on the <u>Run</u> tab of the action: ![Gatsby CLI action configuration](/guides/images/gatsby/gatsby-11.png 640x656) <Hint type="info"> If your build requires additional tools or packages, you can install them in the <u>Environment</u> tab. The listed items will be downloaded on the first pipeline execution and will stay cached in the container. </Hint> The next step should deploy our site to Netlify. Buddy has a dedicated integration with Netlify available in the [IaaS section](/actions/deploy-to-iaas) of the action roster: ![Netlify action location](/guides/images/gatsby/gatsby-12.png 640x321) You will need to modify the action to use your public folder for deployment. Also, remember to select the previously added site: ![Netlify action configuration](/guides/images/gatsby/gatsby-13.png 640x675) With the build and deployment configured, the pipeline should look like this: ![Example deployment pipeline](/guides/images/gatsby/gatsby-14.png 640x462) It's time to give the pipeline a test run! ### Testing pipeline Click the <u>Run pipeline</u> button to trigger it manually. Alternatively, you can push something to the repository. 1. In the first step, Buddy will prepare the environment: fetch the changes from your repository and set up ENV VARs. 2. In the second step, it will download dependencies required by Gatsby and prepare the build for deployment. 3. In the third step, it will connect to Netlify and deploy the site. ![Execution details](/guides/images/gatsby/gatsby-15.png 640x974) <Hint type="tip"> You can expand logs from each step to examine the details. </Hint> ## Summary The guide above covers the two core features of Buddy: build and deployment automation. However, each pipeline can be easily expanded with extra features that will help you deliver better quality websites much faster. You can, for example, add a [Lighthouse](/actions/lighthouse) that will check your website performance after each deployment, or add a [notification](/actions/notifications) action that will inform you when something goes wrong. There are tons of possibilities and they are yours to explore. Go wild! ![Expanded delivery pipeline](/guides/images/gatsby/gatsby-16.png 640x578)
Jarek Dylewski

Jarek Dylewski

Customer Support

A journalist and an SEO specialist trying to find himself in the unforgiving world of coders. Gamer, a non-fiction literature fan and obsessive carnivore. Jarek uses his talents to convert the programming lingo into a cohesive and approachable narration.