Build a static blog using Gatsby and Strapi
Published: 2021-02-15 14:12:30

## Introduction


If you are familiar with our [blog](https://strapi.io/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: [React](https://strapi.io/blog/build-a-blog-with-react-strapi-and-apollo), [Next.js](https://strapi.io/blog/build-a-blog-with-next-react-js-strapi-and-apollo), [Vue](https://strapi.io/blog/build-a-blog-with-vue-strapi-and-apollo), [Nuxt.js](https://strapi.io/blog/build-a-blog-using-nuxt-strapi-and-apollo) or [Angular](https://strapi.io/blog/build-a-blog-with-angular-js-strapi-and-apollo).

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

### Why gatsby?

[Gatsby](https://www.gatsbyjs.org) is a *blazing-fast **website framework** for React*. It allows developers to build React based websites within minutes. Whether you want to develop a blog or a corporate website, Gatsby will fill your needs.

<a href="https://www.gatsbyjs.org"><img style="width:50%" alt="Gatsby logo" src="https://assets.strapi.io/uploads/34450329-a494355a-ed06-11e7-9d0a-30aefeb2fd57.jpg_69231d6fc5.jpeg"></a>

Because it is based on React, the website pages are never reloaded which makes the generated website super fast. A large set of plugins is available to allowing developers to save time coding. For example, plugins exist to get data from any source (Markdown files, CMS, etc.). Gatsby is strongly based on the ["node" interface](https://www.gatsbyjs.org/docs/node-interface/), which is the center of Gatsby's data system.

Created by [Kyle Mathews](https://twitter.com/kylemathews), the project was officially [released in July 2017](https://www.gatsbyjs.org/blog/gatsby-v1/). (As of February 2109,[Gatsby is in Gatsby v2](https://github.com/gatsbyjs/gatsby/blob/master/README.md) and is now [used by many companies and for hundreds of websites](https://www.gatsbyjs.org/showcase/).

### What is Strapi?

[Strapi](https://strapi.io) is the *open source **Headless CMS ***. It saves weeks of API development time, and allows easy long-term content management through a beautiful administration panel _anyone can use_.

Unlike other CMSs, **Strapi is 100% open-source**, which means:

 - **Strapi is completely free**.
 - You can **host it on your own servers**, so you own the data.
 - It is entirely **customisable and extensible**, thanks to the plugin system.

[Try live demo](https://strapi.io/demo)

## Starters

![Gatsby blog](https://assets.strapi.io/uploads/Capture-d-e-cran-2020-01-29-a--16.53.48--1-.png_5870e6eb94.png)

You may want to directly try the result of this tutorial. Well, we made a [starter](https://strapi.io/starters) 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](https://github.com/strapi/strapi-starter-gatsby-blog).

This tutorial is an update of the old version. We will make this tutorial shorter and more efficient by using our [new templates](https://github.com/strapi?q=strapi-template&type=&language=).

## Prerequisites

This tutorial will always use the **latest version of Strapi**. That is awesome right!? You'll understand why below.
You need to have node v.12 installed and that's all.

## Setup

  - Create a blog-strapi folder and get inside!

`take blog-strapi`


### Back-end setup

That's the easiest part of this tutorial thanks to Rémi who developed a series of [Strapi templates](https://github.com/strapi?q=strapi-template&type=&language=) that you can use for your [Blog](https://github.com/strapi/strapi-template-blog), [E-commerce](https://github.com/strapi/strapi-template-ecommerce), [Portfolio](https://github.com/strapi/strapi-template-portfolio) or [Corporate](https://github.com/strapi/strapi-template-corporate) website project.

These templates are Strapi applications containing existing collection-types and single-types suited for the appropriate use case, and data.
In this tutorial we'll use the Blog template and connect a Gatsby application to it.

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

  - Create your Strapi backend folder using the Blog template.

`yarn create strapi-app backend  --quickstart --template https://github.com/strapi/strapi-template-blog`

![Admin](https://assets.strapi.io/uploads/template_blog_admin_425eca3a96.png)

Don't forget that Strapi is running on [http://localhost:1337](http://localhost:1337). Create your admin user by signing up!

That's it! You're done with Strapi! I'm not kidding, we can start to create our Gatsby application now in order to fetch our content from Strapi.
Ok ok wait, let's talk about this amazing template you just created.

You should know that before the [starters](https://github.com/strapi?q=starter&type=&language=) and before the [templates](https://github.com/strapi?q=strapi-template&type=&language=) we only had tutorials. The idea of creating starters came to us when we realized that we could do something with the end result of our tutorials. Thus were born our starters.

However Strapi evolves quickly, very quickly and at the time the starters constituted a repository including the backend as well as the frontend. This means that updating the Strapi version on all our starters took time, too much time. We then decided to develop templates that are always created with the latest versions of Strapi. Quite simply by passing in parameter the repository of the desired template like you just did. Also, it gives you a good architecture for your Strapi project.

These templates provide a solid basis for your Strapi application.
  - 3 Collection types are already created. Article, Category, Writer.
  - 2 Single types are already created. Global, Homepage.
  - Find and FindOne permissions are publicly open for all of you content-types types and single types.
  - Existing data.

![Admin Home](https://assets.strapi.io/uploads/template_blog_admin_home_15598b80df.png)

![Global](https://assets.strapi.io/uploads/template_blog_global_4d3da8604d.png)

![Articles](https://assets.strapi.io/uploads/template_blog_articles_2035d5904c.png)

![Permissions](https://assets.strapi.io/uploads/template_blog_permissions_2668790889.png)

Feel free to modify all this, however, we will be satisfied with that for the tutorial.

**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:

  `yarn global add 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
```

![](https://assets.strapi.io/uploads/Capture-d-e-cran-2020-01-28-a--11.13.01.png_84bc1f47ac.png)

We are not keeping the default Gatsby starter style but the one we used for every other tutorials of this series. You'll see that after! First let's create an `.env` file containing some environment variables for our Gatsby application.

  - Create ann `.env` file at the root of your Gatsby application containing the following:

```  
GATSBY_ROOT_URL=http://localhost:8000
API_URL=http://localhost:1337
```

**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:

```
require("dotenv").config({
  path: `.env`,
});

module.exports = {
  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: ["article", "category", "writer"],
        singleTypes: [`homepage`, `global`],
        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",
        icon: `src/images/gatsby-icon.png`
      },
    },
    "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 collection types and single types you want to be able to query from Strapi, here for this tutorial: **article**, **category**, **writer**, **global** and **homepage**

**Alright!** Gatsby is now ready to fetch data from Strapi! Let's add our custom css now!


**UIkit setup**

Gatsby generate by default a `Seo` component. We will update it in order to configure our Seo, add UIkit cdn and Staatliches font.

  - Replace the content of `src/components/seo.js` with the following:

```
import React from "react";
import PropTypes from "prop-types";
import { Helmet } from "react-helmet";
import { useStaticQuery, graphql } from "gatsby";

const SEO = ({ seo = {} }) => {
  const { strapiGlobal } = useStaticQuery(query);
  const { defaultSeo, siteName, favicon } = strapiGlobal;

  // Merge default and page-specific SEO values
  const fullSeo = { ...defaultSeo, ...seo };

  const getMetaTags = () => {
    const tags = [];

    if (fullSeo.metaTitle) {
      tags.push(
        {
          property: "og:title",
          content: fullSeo.metaTitle,
        },
        {
          name: "twitter:title",
          content: fullSeo.metaTitle,
        }
      );
    }
    if (fullSeo.metaDescription) {
      tags.push(
        {
          name: "description",
          content: fullSeo.metaDescription,
        },
        {
          property: "og:description",
          content: fullSeo.metaDescription,
        },
        {
          name: "twitter:description",
          content: fullSeo.metaDescription,
        }
      );
    }
    if (fullSeo.shareImage) {
      const imageUrl =
        (process.env.GATSBY_ROOT_URL || "http://localhost:8000") +
        fullSeo.shareImage.publicURL;
      tags.push(
        {
          name: "image",
          content: imageUrl,
        },
        {
          property: "og:image",
          content: imageUrl,
        },
        {
          name: "twitter:image",
          content: imageUrl,
        }
      );
    }
    if (fullSeo.article) {
      tags.push({
        property: "og:type",
        content: "article",
      });
    }
    tags.push({ name: "twitter:card", content: "summary_large_image" });

    return tags;
  };

  const metaTags = getMetaTags();

  return (
    <Helmet
      title={fullSeo.metaTitle}
      titleTemplate={`%s | ${siteName}`}
      link={[
        {
          rel: "icon",
          href: favicon.publicURL,
        },
        {
          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",
        },
      ]}
      meta={metaTags}
    />
  );
};

export default SEO;

SEO.propTypes = {
  title: PropTypes.string,
  description: PropTypes.string,
  image: PropTypes.string,
  article: PropTypes.bool,
};

SEO.defaultProps = {
  title: null,
  description: null,
  image: null,
  article: false,
};

const query = graphql`
  query {
    strapiGlobal {
      siteName
      favicon {
        publicURL
      }
      defaultSeo {
        metaTitle
        metaDescription
        shareImage {
          publicURL
        }
      }
    }
  }
`;
```

Wow! That's a lot of code! Here we are simply fetching the content of the **Global** collection type.

```
const query = graphql`
  query {
    strapiGlobal {
      siteName
      favicon {
        publicURL
      }
      defaultSeo {
        metaTitle
        metaDescription
        shareImage {
          publicURL
        }
      }
    }
  }
`;
```

This way, we can use this data in our SEO component.

```
const SEO = ({ seo = {} }) => {
  const { strapiGlobal } = useStaticQuery(query);
  const { defaultSeo, siteName, favicon } = strapiGlobal;
  ...
```

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!

#### 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 src/pages/using-typescript.tsx`


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 { graphql, useStaticQuery } from "gatsby";
import Layout from "../components/layout";
import "../assets/css/main.css";

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

  return (
    <Layout seo={data.strapiHomepage.seo}>
      <div className="uk-section">
        <div className="uk-container uk-container-large">
          <h1>{data.strapiHomepage.hero.title}</h1>
        </div>
      </div>
    </Layout>
  );
};

const query = graphql`
  query {
    strapiHomepage {
      hero {
        title
      }
      seo {
        metaTitle
        metaDescription
        shareImage {
          publicURL
        }
      }
    }
  }
`;

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 { StaticQuery, graphql } from "gatsby";
import Seo from "./seo";

const Layout = ({ children, seo }) => (
  <StaticQuery
    query={graphql`
      query {
        strapiHomepage {
          seo {
            metaTitle
            metaDescription
            shareImage {
              publicURL
            }
          }
        }
      }
    `}
    render={(data) => (
      <>
        <Seo seo={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.
You can already check out the result! You must simply have a blank page containing a h1 'My Blog'!

![My Blog](https://assets.strapi.io/uploads/template_blog_3bed456cfa.png)

#### Nav component

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 = () => (
  <StaticQuery
    query={graphql`
      query {
        strapiGlobal {
          siteName
        }
        allStrapiCategory {
          edges {
            node {
              slug
              name
            }
          }
        }
      }
    `}
    render={(data) => (
      <div>
        <div>
          <nav className="uk-navbar-container" data-uk-navbar>
            <div className="uk-navbar-left">
              <ul className="uk-navbar-nav">
                <li>
                  <Link to="/">{data.strapiGlobal.siteName}</Link>
                </li>
              </ul>
            </div>
            <div className="uk-navbar-right">
              <button
                className="uk-button uk-button-default uk-margin-right"
                type="button"
              >
                Categories
              </button>
              <div uk-dropdown="animation: uk-animation-slide-top-small; duration: 1000">
                <ul className="uk-nav uk-dropdown-nav">
                  {data.allStrapiCategory.edges.map((category, i) => (
                    <li key={`category__${category.node.slug}`}>
                      <Link to={`/category/${category.node.slug}`}>
                        {category.node.name}
                      </Link>
                    </li>
                  ))}
                </ul>
              </div>
            </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 {
        slug
        name
      }
    }
  }
}
```

This one fetch all your categories from Strapi.
Then `StaticQuery` render your html with the fetched data.

```
...
render={(data) => (
...
```

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 { StaticQuery, graphql } from "gatsby";
import Nav from "./nav";
import Seo from "./seo";

const Layout = ({ children, seo }) => (
  <StaticQuery
    query={graphql`
      query {
        strapiHomepage {
          seo {
            metaTitle
            metaDescription
            shareImage {
              publicURL
            }
          }
        }
      }
    `}
    render={(data) => (
      <>
        <Seo seo={seo} />
        <Nav />
        <main>{children}</main>
      </>
    )}
  />
);

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

export default Layout;
```

**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.

![Navbar](https://assets.strapi.io/uploads/template_blog_nav_5fe3b620d9.png)

**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 { graphql, useStaticQuery } from "gatsby";
import Layout from "../components/layout";
import ArticlesComponent from "../components/articles";
import "../assets/css/main.css";

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

  return (
    <Layout seo={data.strapiHomepage.seo}>
      <div className="uk-section">
        <div className="uk-container uk-container-large">
          <h1>{data.strapiHomepage.hero.title}</h1>
          <ArticlesComponent articles={data.allStrapiArticle.edges} />
        </div>
      </div>
    </Layout>
  );
};

const query = graphql`
  query {
    strapiHomepage {
      hero {
        title
      }
      seo {
        metaTitle
        metaDescription
        shareImage {
          publicURL
        }
      }
    }
    allStrapiArticle(filter: { status: { eq: "published" } }) {
      edges {
        node {
          strapiId
          slug
          title
          category {
            name
          }
          image {
            childImageSharp {
              fixed(width: 800, height: 500) {
                src
              }
            }
          }
          author {
            name
            picture {
              childImageSharp {
                fixed(width: 30, height: 30) {
                  src
                }
              }
            }
          }
        }
      }
    }
  }
`;

export default IndexPage;
```

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

Fetching articles:

```
allStrapiArticle(filter: { status: { eq: "published" } }) {
      edges {
        node {
          strapiId
          slug
          title
          category {
            name
          }
          image {
            childImageSharp {
              fixed(width: 800, height: 500) {
                src
              }
            }
          }
          author {
            name
            picture {
              childImageSharp {
                fixed(width: 30, height: 30) {
                  src
                }
              }
            }
          }
        }
      }
    }
```

Render them using a component that we are going to create:

```
<ArticlesComponent articles={data.allStrapiArticle.edges} />
```


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@s" data-uk-grid="true">
        <div>
          {leftArticles.map((article, i) => {
            return (
              <Card
                article={article}
                key={`article__left__${article.node.slug}`}
              />
            );
          })}
        </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__right__${article.node.slug}`}
                />
              );
            })}
          </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";
import Img from "gatsby-image";

const Card = ({ article }) => {
  return (
    <Link to={`/article/${article.node.slug}`} className="uk-link-reset">
      <div className="uk-card uk-card-muted">
        <div className="uk-card-media-top">
          <Img
            fixed={article.node.image.childImageSharp.fixed}
            imgStyle={{ position: "static" }}
          />
        </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>
            <hr className="uk-divider-small" />
            <div className="uk-grid-small uk-flex-left" data-uk-grid="true">
              <div>
                {article.node.author.picture && (
                  <Img
                    fixed={article.node.author.picture.childImageSharp.fixed}
                    imgStyle={{ position: "static", borderRadius: "50%" }}
                  />
                )}
              </div>
              <div className="uk-width-expand">
                <p className="uk-margin-remove-bottom">
                  {article.node.author.name}
                </p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Link>
  );
};

export default Card;
```

  - Refresh your app

![Gatsby blog](https://assets.strapi.io/uploads/Capture-d-e-cran-2020-01-29-a--16.53.48--1-.png_8a775dfc1a.png)

**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
              slug
            }
          }
        }
      }
    `
  );

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

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

  const ArticleTemplate = require.resolve("./src/templates/article.js");

  articles.forEach((article, index) => {
    createPage({
      path: `/article/${article.node.slug}`,
      component: ArticleTemplate,
      context: {
        slug: article.node.slug,
      },
    });
  });
};

module.exports.onCreateNode = async ({ node, actions, createNodeId }) => {
  const crypto = require(`crypto`);

  if (node.internal.type === "StrapiArticle") {
    const newNode = {
      id: createNodeId(`StrapiArticleContent-${node.id}`),
      parent: node.id,
      children: [],
      internal: {
        content: node.content || " ",
        type: "StrapiArticleContent",
        mediaType: "text/markdown",
        contentDigest: crypto
          .createHash("md5")
          .update(node.content || " ")
          .digest("hex"),
      },
    };
    actions.createNode(newNode);
    actions.createParentChildLink({
      parent: node,
      child: newNode,
    });
  }
};
```

We are fetching all article and category slugs 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.slug}`:

```
/article/my-article
/article/another-article
/article/hey-strapi
/article/love-gatsby
```

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 `const ArticleTemplate = 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 Img from "gatsby-image";
import Moment from "react-moment";
import Layout from "../components/layout";
import Markdown from "react-markdown";

export const query = graphql`
  query ArticleQuery($slug: String!) {
    strapiArticle(slug: { eq: $slug }, status: { eq: "published" }) {
      strapiId
      title
      description
      content
      publishedAt
      image {
        publicURL
        childImageSharp {
          fixed {
            src
          }
        }
      }
      author {
        name
        picture {
          childImageSharp {
            fixed(width: 30, height: 30) {
              src
            }
          }
        }
      }
    }
  }
`;

const Article = ({ data }) => {
  const article = data.strapiArticle;
  const seo = {
    metaTitle: article.title,
    metaDescription: article.description,
    shareImage: article.image,
    article: true,
  };

  return (
    <Layout seo={seo}>
      <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">
            <Markdown source={article.content} escapeHtml={false} />

            <hr className="uk-divider-small" />

            <div className="uk-grid-small uk-flex-left" data-uk-grid="true">
              <div>
                {article.author.picture && (
                  <Img
                    fixed={article.author.picture.childImageSharp.fixed}
                    imgStyle={{ position: "static", borderRadius: "50%" }}
                  />
                )}
              </div>
              <div className="uk-width-expand">
                <p className="uk-margin-remove-bottom">
                  By {article.author.name}
                </p>
                <p className="uk-text-meta uk-margin-remove-top">
                  <Moment format="MMM Do YYYY">{article.published_at}</Moment>
                </p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </Layout>
  );
};

export default Article;
```

  - **Restart your Gatsby server** (you modified your `gatsby.node.js` file)

![Article page](https://assets.strapi.io/uploads/Capture-d-e-cran-2020-01-29-a--16.54.21.png_452acf4ba9.png)

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
              slug
            }
          }
        }
        categories: allStrapiCategory {
          edges {
            node {
              strapiId
              slug
            }
          }
        }
      }
    `
  );

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

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

  const ArticleTemplate = require.resolve("./src/templates/article.js");

  articles.forEach((article, index) => {
    createPage({
      path: `/article/${article.node.slug}`,
      component: ArticleTemplate,
      context: {
        slug: article.node.slug,
      },
    });
  });

  const CategoryTemplate = require.resolve("./src/templates/category.js");

  categories.forEach((category, index) => {
    createPage({
      path: `/category/${category.node.slug}`,
      component: CategoryTemplate,
      context: {
        slug: category.node.slug,
      },
    });
  });
};

module.exports.onCreateNode = async ({ node, actions, createNodeId }) => {
  const crypto = require(`crypto`);

  if (node.internal.type === "StrapiArticle") {
    const newNode = {
      id: createNodeId(`StrapiArticleContent-${node.id}`),
      parent: node.id,
      children: [],
      internal: {
        content: node.content || " ",
        type: "StrapiArticleContent",
        mediaType: "text/markdown",
        contentDigest: crypto
          .createHash("md5")
          .update(node.content || " ")
          .digest("hex"),
      },
    };
    actions.createNode(newNode);
    actions.createParentChildLink({
      parent: node,
      child: newNode,
    });
  }
};
```

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($slug: String!) {
    articles: allStrapiArticle(
      filter: { status: { eq: "published" }, category: { slug: { eq: $slug } } }
    ) {
      edges {
        node {
          slug
          title
          category {
            name
          }
          image {
            childImageSharp {
              fixed(width: 660) {
                src
              }
            }
          }
          author {
            name
            picture {
              childImageSharp {
                fixed(width: 30, height: 30) {
                  ...GatsbyImageSharpFixed
                }
              }
            }
          }
        }
      }
    }
    category: strapiCategory(slug: { eq: $slug }) {
      name
    }
  }
`;

const Category = ({ data }) => {
  const articles = data.articles.edges;
  const category = data.category.name;
  const seo = {
    metaTitle: category,
    metaDescription: `All ${category} articles`,
  };

  return (
    <Layout seo={seo}>
      <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](https://raw.githubusercontent.com/strapi/strapi-starter-gatsby-blog/master/screenshot.png)

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](https://media.giphy.com/media/LLHkw7UnvY3Kw/giphy.gif)

**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](https://strapi.io/documentation/3.0.0-alpha.x/guides/deployment.html#configuration).

### 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

> **Please note:** Since we initially published this blog, we released new versions of Strapi and tutorials may be outdated. Sorry for the inconvenience if it's the case, and please help us [by reporting it here](https://github.com/strapi/community-content/issues/new/choose).

  - [Try live demo](https://strapi.io/demo)
  - [Starters](https://strapi.io/starters)
  - [Become a Strapi expert](https://academy.strapi.io/)
  - [Find help in our Forum](https://forum.strapi.io/)
  - [Strapi on Youtube](https://www.youtube.com/c/Strapi/featured)
  - [Try Enterprise Edition](https://strapi.io/enterprise)