Introduction

If you are familiar with our blog you must have seen that we've released a series of tutorials on how to make blogs using Strapi with a lot of frontend frameworks: Gatsby Old, React, Next.js, Vue, Nuxt or Angular.

This one if for the people who wants to build a simple static blog with Gatsby!

Gatsby blog

Starter

You may want to directly try the result of this tutorial. Well, we made a starter out of it so give it a try!

Goal

The goal here is to be able to create a simple static blog website using Strapi as the backend and Gatsby for the frontend

The source code is available on GitHub.

Prerequisites

This tutorial uses Strapi v3.0.0-beta.18.3.

You need to have node v.12 installed and that's all.

Setup

  • Create a blog-strapi folder and get inside!

mkdir blog-strapi && cd blog-strapi

Back-end setup

That's the easiest part, as since beta.9 Strapi has an excellent package create strapi-app that allows you to create a Strapi project in seconds without needing to install Strapi globally so let's try it out.

Note: for this tutorial, we will use yarn as your package manager.

  • yarn create strapi-app backend --quickstart --no-run.

This single command line will create all you need for your back-end. Make sure to add the --no-run flag as it will prevent your app from automatically starting the server because SPOILER ALERT: we need to install some awesome Strapi plugins.

Now that you know that we need to install some plugins to enhance your app, let's install one of our most popular ones: the graphql plugin.

  • yarn strapi install graphql

Once the installation is completed, you can finally start your Strapi server strapi dev and create your first Administrator.

Strapi Admin creation

Don't forget that Strapi is running on http://localhost:1337

Nice! Now that Strapi is ready, you are going to create your Gatsby application.

Front-end setup

The easiest part has been completed, let's get our hands dirty developing our blog with Gatsby!

Gatsby setup

First of all, you'll need to install the Gatsby CLI

  • Install the Gatsby CLI by running the following command:

    npm install -g gatsby-cli

  • Create a Gatsby frontend project by running the following command:

    gatsby new frontend

Once the installation is completed, you can start your front-end app to make sure everything went ok.

cd frontend
gatsby develop

We are not keeping the default Gatsby starter style but the one we used for every other tutorials of this series

UIkit setup

Gatsby generate by default a Seo component that will allow us to add UIkit cdn and Staatliches font

  • Add the following props to your components/seo.js file:
...
link={[  
  {
    rel: "stylesheet",
    href: "https://fonts.googleapis.com/css?family=Staatliches",
  },
  {
    rel: "stylesheet",
    href:
      "https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/css/uikit.min.css",
  },
]}
script={[  
  {
    src:
      "https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.min.js",
  },
  {
    src:
      "https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/js/uikit-icons.min.js",
  },
  {
    src: "https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.js",
  },
]}
...

In order to have a simple but beautiful blog we're going to add some style:

  • Create a src/assets/css/main.css file containing the following:
a {  
  text-decoration: none !important;
}

h1 {  
  font-family: Staatliches !important;
  font-size: 120px !important;
}

#category {
  font-family: Staatliches !important;
  font-weight: 500 !important;
}

#title {
  letter-spacing: 0.4px !important;
  font-size: 22px !important;
  font-size: 1.375rem !important;
  line-height: 1.13636 !important;
}

#banner {
  margin: 20px !important;
  height: 800px !important;
}

#editor {
  font-size: 16px !important;
  font-size: 1rem !important;
  line-height: 1.75 !important;
}

.uk-navbar-container {
  background: #fff !important;
  font-family: Staatliches !important;
}

img:hover {  
  opacity: 1 !important;
  transition: opacity 0.25s cubic-bezier(0.39, 0.575, 0.565, 1) !important;
}

Please don't force me to explain you some css!

Strapi Setup

To connect Gatsby to a new source of data, you have to develop a new source plugin. Fortunately, several source plugins already exist, so one of them should fill your needs.

In this example, we are using Strapi. Obviously, we are going to need a source plugin for Strapi APIs. Good news: we built it for you!

  • Install gatsby-source-strapi by running the following command:

    yarn add gatsby-source-strapi

This plugin needs to be configured.

  • Replace the content of gatsby-config.js with the following code:
module.exports = {  
  siteMetadata: {
    title: "My super blog",
    description: "Gatsby blog with Strapi",
    author: "Strapi team",
  },
  plugins: [
    "gatsby-plugin-react-helmet",
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: "gatsby-source-strapi",
      options: {
        apiURL: "http://localhost:1337",
        contentTypes: [
          // List of the Content Types you want to be able to request from Gatsby.
          "article",
          "category",
        ],
        queryLimit: 1000,
      },
    },
    "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",
      },
    },
    "gatsby-plugin-offline",
  ],
}

Actually, we are going to use environment variables for our API url:

  • Create a .env.development file a the root of your frontend folder containing the following:

API_URL="http://localhost:1337"

  • Replace the code of your gatsby.config.js by the following one:
require("dotenv").config({  
  path: `.env.${process.env.NODE_ENV}`,
})

module.exports = {  
  siteMetadata: {
    title: "My super blog",
    description: "Gatsby blog with Strapi",
    author: "Strapi team",
  },
  plugins: [
    "gatsby-plugin-react-helmet",
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: "gatsby-source-strapi",
      options: {
        apiURL: process.env.API_URL || "http://localhost:1337",
        contentTypes: [
          // List of the Content Types you want to be able to request from Gatsby.
          "article",
          "category",
        ],
        queryLimit: 1000,
      },
    },
    "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",
      },
    },
    "gatsby-plugin-offline",
  ],
}

What's important here is that we define our API_URL for the Strapi API: http://localhost:1337 without a trailling slash and the content types you want to be able to query from Strapi, here for this tutorial: article and category

Alright! Gatsby is now ready to fetch data from Strapi, but first, we need to create some on Strapi!

Designing the data structure

Finally! We are now going to create the data structure of our article by creating an Article content type.

  • Dive in your Strapi admin panel and click on the Content Type Builder link in the sidebar.

Strapi Content Type Builder

  • Click on Create new content-type and call it article.

Strapi - add a content type

Now you'll be asked to create all the fields for your content-type:

Strapi - add fields to content type

  • Create the following ones:
    • title with type Text (required)
    • content with type Rich Text (required)
    • image with type Media (Single image) and (required)
    • published_at with type date (required)

Press Save! Here you go, your first content type has been created. Now you may want to create your first article, but we have one thing to do before that: Grant access to the article content type.

  • Click on the Roles & Permission and click on the public role.
  • Check the article find and findone routes and save.

Strapi opening access

Awesome! You should be ready to create your first article right now and fetch it on the GraphQL Playground.

  • Now, create your first article !

Here's an example:
Strapi example of article

Great! Now you may want to reach the moment when you can actually fetch your articles through the API!

Isn't that cool! You can also play with the GraphQL Playground.

GraphQL playground

Create categories

You may want to assign a category to your article (news, trends, opinion). You are going to do this by creating another content type in Strapi.

  • Create a category content type with the following field
    • name with type Text

Press save!

  • Create a new field in the Article content type which is a Relation Category has many Articles like below:

Strapi relational fields

  • Click on the Roles & Permission and click on the public role. And check the category find and findone routes and save.

Now you'll be able to select a category for your article in the right sidebox.

Category on right side

Now that we are good with Strapi let's work on the frontend part!

Architecture

Before we can dive in, we have to clean the default Gatsby architecture by removing useless components/pages

  • Remove useless components/pages by running the following command:

    rm src/components/header.js src/components/layout.css src/components/image.js src/pages/page-2.js

We are also going to update the pages/index.js and components/layout.js files

  • Replace the content of pages/index.js by the following code:
import React from "react"

import Layout from "../components/layout"

import "../assets/css/main.css"

const IndexPage = () => <Layout></Layout>

export default IndexPage  

Here, we are just importing the Layout component and the css file, this is the main page of your app.

  • Replace the content of components/layout.js by the following code:
import React from "react"  
import PropTypes from "prop-types"

import Seo from "./seo"

const Layout = ({ children }) => {  
  return (
    <>
      <Seo />
      <main>{children}</main>
    </>
  )
}

Layout.propTypes = {  
  children: PropTypes.node.isRequired,
}

export default Layout  

What is the Layouts component in Gatsby ? These components are for sections of your site that you want to share across multiple pages. For example, Gatsby sites will commonly have a layout component with a shared header and footer. Other common things to add to layouts are a sidebar and/or navigation menu.

We'll use this layout in order to put our Nav component inside

Let's create our Nav that will display all your categories on top of every pages of your blog

  • Create a ./src/components/nav.js file containing the following code:
import React from "react"  
import { Link, StaticQuery, graphql } from "gatsby"

const Nav = () => (  
  <div>
    <div>
      <nav className="uk-navbar-container" data-uk-navbar>
        <div className="uk-navbar-left">
          <ul className="uk-navbar-nav">
            <li>
              <Link to="/">Strapi Blog</Link>
            </li>
          </ul>
        </div>

        <div className="uk-navbar-right">
          <ul className="uk-navbar-nav">
            <StaticQuery
              query={graphql`
                query {
                  allStrapiCategory {
                    edges {
                      node {
                        strapiId
                        name
                      }
                    }
                  }
                }
              `}
              render={data =>
                data.allStrapiCategory.edges.map((category, i) => {
                  return (
                    <li key={category.node.strapiId}>
                      <Link to={`/category/${category.node.strapiId}`}>
                        {category.node.name}
                      </Link>
                    </li>
                  )
                })
              }
            />
          </ul>
        </div>
      </nav>
    </div>
  </div>
)

export default Nav  

What is going on here ? Let me explain

Gatsby v2 introduces StaticQuery, a new API that allows components to retrieve data via a GraphQL query

query {  
  allStrapiCategory {
    edges {
      node {
        strapiId
        name
      }
    }
  }
}

This one fetch all your articles from Strapi

Then StaticQuery render your html with the fetched data:

render={data =>  
  data.allStrapiCategory.edges.map((category, i) => {
    return (
      <li key={category.node.strapiId}>
        <Link to={`/category/${category.node.strapiId}`}>
          {category.node.name}
        </Link>
      </li>
    )
  })
}

Now we need to use this component in your Layout component.

  • Import and use your new Nav component by pasting the following code inside your components/layout.js file:
import React from "react"  
import PropTypes from "prop-types"

import Nav from "./nav"  
import Seo from "./seo"

const Layout = ({ children }) => {  
  return (
    <>
      <Seo />
      <Nav />
      <main>{children}</main>
    </>
  )
}

Layout.propTypes = {  
  children: PropTypes.node.isRequired,
}

export default Layout  

Don't forget to create some categories!

Great! You should now be able to see your brand new nav containing your categories. But the links are not working right now. We'll fix this later on the tutorial, don't worry.

Note: The current code is not suited to display a lot of categories as you may encounter a UI issue. Since this blog post is supposed to be short, you could improve the code by adding a lazy load or something like that.

Articles component

We are going to display every articles on your main pages/index.js page now:

  • Replace the code of the pages/index.js file with the following code:
import React from "react"  
import { StaticQuery, graphql } from "gatsby"

import Layout from "../components/layout"  
import ArticlesComponent from "../components/articles"

import "../assets/css/main.css"

const IndexPage = () => (  
  <Layout>
    <StaticQuery
      query={graphql`
        query {
          allStrapiArticle {
            edges {
              node {
                strapiId
                title
                category {
                  name
                }
                image {
                  publicURL
                }
              }
            }
          }
        }
      `}
      render={data => (
        <div className="uk-section">
          <div className="uk-container uk-container-large">
            <h1>Strapi blog</h1>
            <ArticlesComponent articles={data.allStrapiArticle.edges} />
          </div>
        </div>
      )}
    />
  </Layout>
)

export default IndexPage  

As you can see, we are fetching the articles with StaticQuery that render them using this ArticlesComponent.

In fact we are going to separate our articles from left to right and we don't want this code to be inside the index.js but in a component that we'll be able to reuse after that.

  • Create a components/articles.js containing the following code:
import React from "react"  
import Card from "./card"

const Articles = ({ articles }) => {  
  const leftArticlesCount = Math.ceil(articles.length / 5)
  const leftArticles = articles.slice(0, leftArticlesCount)
  const rightArticles = articles.slice(leftArticlesCount, articles.length)

  return (
    <div>
      <div className="uk-child-width-1-2" data-uk-grid>
        <div>
          {leftArticles.map((article, i) => {
            return (
              <Card article={article} key={`article__${article.node.id}`} />
            )
          })}
        </div>
        <div>
          <div className="uk-child-width-1-2@m uk-grid-match" data-uk-grid>
            {rightArticles.map((article, i) => {
              return (
                <Card article={article} key={`article__${article.node.id}`} />
              )
            })}
          </div>
        </div>
      </div>
    </div>
  )
}

export default Articles  

Here we are splitting our articles because we want to display some of them on the left side of the blog making them bigger and some on the right making them smaller. It's just a design thing, nothing really important

Again, we're using another Card component. Components are the building blocks of any React application, this is why you'll find many of these in them.

The benefit to this approach is that you end up doing less duplicate work by managing the components rather than managing duplicate content across different pages. This concept has become a real success and you are going to use it too!

  • Create a components/card.js file containing the following code:
import React from "react"  
import { Link } from "gatsby"

const Card = ({ article }) => {  
  return (
    <Link to={`/article/${article.node.strapiId}`} className="uk-link-reset">
      <div className="uk-card uk-card-muted">
        <div className="uk-card-media-top">
          <img
            src={article.node.image.publicURL}
            alt={article.node.image.publicURL}
            height="100"
          />
        </div>
        <div className="uk-card-body">
          <p id="category" className="uk-text-uppercase">
            {article.node.category.name}
          </p>
          <p id="title" className="uk-text-large">
            {article.node.title}
          </p>
        </div>
      </div>
    </Link>
  )
}

export default Card  
  • Refresh your app

Gatsby blog

Awesome! You can now display all your articles on the main page!

Article page

How can we create a page for each one of your article now? We'll need to use the createPages API. It will be called once the data layer is bootstrapped to let plugins create pages from data.

  • Add the following code to your gatsby.node.js file:
exports.createPages = async ({ graphql, actions }) => {  
  const { createPage } = actions
  const result = await graphql(
    `
      {
        articles: allStrapiArticle {
          edges {
            node {
              strapiId
            }
          }
        }
      }
    `
  )

  if (result.errors) {
    throw result.errors
  }

  // Create blog articles pages.
  const articles = result.data.articles.edges
  articles.forEach((article, index) => {
    createPage({
      path: `/article/${article.node.strapiId}`,
      component: require.resolve("./src/templates/article.js"),
      context: {
        id: article.node.strapiId,
      },
    })
  })
}

We are fetching all your article and category ids in order to generate pages for each one of them
It means that if you have 4 articles it will create 4 pages depending on the path you give, here it's path: /article/${article.node.strapiId}:

/article/1
/article/2
/article/3
/article/4

Before going further you'll need to install two packages: one to display your content as Markdown and Moment

  • Install react-markdown and react-moment by running the following command:

    yarn add react-markdown react-moment moment

Now we need to create the page that will display each one of your article, you can see inside gatsby.node.js that here it's component: require.resolve("./src/templates/article.js")

  • Create a src/templates/article.js file containing the following:
import React from "react"  
import { graphql } from "gatsby"

import ReactMarkdown from "react-markdown"  
import Moment from "react-moment"

import Layout from "../components/layout"

export const query = graphql`  
  query ArticleQuery($id: Int!) {
    strapiArticle(strapiId: { eq: $id }) {
      strapiId
      title
      content
      published_at
      image {
        publicURL
      }
    }
  }
`

const Article = ({ data }) => {  
  const article = data.strapiArticle
  return (
    <Layout>
      <div>
        <div
          id="banner"
          className="uk-height-medium uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding uk-margin"
          data-src={article.image.publicURL}
          data-srcset={article.image.publicURL}
          data-uk-img
        >
          <h1>{article.title}</h1>
        </div>

        <div className="uk-section">
          <div className="uk-container uk-container-small">
            <ReactMarkdown source={article.content} />
            <p>
              <Moment format="MMM Do YYYY">{article.published_at}</Moment>
            </p>
          </div>
        </div>
      </div>
    </Layout>
  )
}

export default Article  
  • Restart your Gatsby server (you modified your gatsby.node.js file)

Article page

You should now be able to visit each one of your article now

Category page

We need to do the exact same process as for the article page.

First of all, we need to generate all the necessary pages. We fetch all your category ids and then we create the pages

  • Replace the content of your gatsby.node.js file with the following code:
exports.createPages = async ({ graphql, actions }) => {  
  const { createPage } = actions
  const result = await graphql(
    `
      {
        articles: allStrapiArticle {
          edges {
            node {
              strapiId
            }
          }
        }
        categories: allStrapiCategory {
          edges {
            node {
              strapiId
            }
          }
        }
      }
    `
  )

  if (result.errors) {
    throw result.errors
  }

  // Create blog articles pages.
  const articles = result.data.articles.edges
  const categories = result.data.categories.edges

  articles.forEach((article, index) => {
    createPage({
      path: `/article/${article.node.strapiId}`,
      component: require.resolve("./src/templates/article.js"),
      context: {
        id: article.node.strapiId,
      },
    })
  })

  categories.forEach((category, index) => {
    createPage({
      path: `/category/${category.node.strapiId}`,
      component: require.resolve("./src/templates/category.js"),
      context: {
        id: category.node.strapiId,
      },
    })
  })
}

As you can see, we are doing the exact same thing as for articles

  • Create a src/templates/category.js file containing the following code:
import React from "react"  
import { graphql } from "gatsby"

import ArticlesComponent from "../components/articles"  
import Layout from "../components/layout"

export const query = graphql`  
  query Category($id: Int!) {
    articles: allStrapiArticle(filter: { category: { id: { eq: $id } } }) {
      edges {
        node {
          strapiId
          title
          category {
            name
          }
          image {
            publicURL
          }
        }
      }
    }
    category: strapiCategory(strapiId: { eq: $id }) {
      name
    }
  }
`

const Category = ({ data }) => {  
  const articles = data.articles.edges
  const category = data.category.name

  return (
    <Layout>
      <div className="uk-section">
        <div className="uk-container uk-container-large">
          <h1>{category}</h1>
          <ArticlesComponent articles={articles} />
        </div>
      </div>
    </Layout>
  )
}

export default Category  

You can see that we are using the ArticlesComponent again! In fact we are displaying articles the same we as in the index.js main file, this is why we created a component, in order to not duplicate code ;)

  • Restart your Gatsby server (you modified your gatsby.node.js file)

Category List

Awesome! You can list articles depending on the selected category now.

Conclusion

Huge congrats, you successfully achieved this tutorial. I hope you enjoyed it!

Congratulations

Still hungry?

Feel free to add additional features, adapt this project to your own needs, and give your feedback in the comment section below.

If you want to deploy your application, check our documentation.

Write for the community

Contribute and collaborate on educational content for the Strapi Community https://strapi.io/write-for-the-community

Can't wait to see your contribution!

One last thing, we are trying to make the best possible tutorials for you, help us in this mission by answering this short survey https://strapisolutions.typeform.com/to/bwXvhA?channel=xxxxx

News in your inbox

Did you enjoy this article? Subscribe to get the latest posts and the most important updates!