DEV Community

Strapi
Strapi

Posted on • Originally published at strapi.io

Build a static blog using Gatsby and Strapi

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: React, Next.js, Vue, Nuxt.js or Angular.

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

Why gatsby?

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

Gatsby logo

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, which is the center of Gatsby's data system.

Created by Kyle Mathews, the project was officially released in July 2017. (As of February 2109,Gatsby is in Gatsby v2 and is now used by many companies and for hundreds of websites.

What is Strapi?

Strapi 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

Starters

Gatsby blog

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.

This tutorial is an update of the old version. We will make this tutorial shorter and more efficient by using our new templates.

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 that you can use for your Blog, E-commerce, Portfolio or 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

Don't forget that Strapi is running on 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 and before the templates 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

Global

Articles

Permissions

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
  ],
};
Enter fullscreen mode Exit fullscreen mode

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
        }
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

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
        }
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

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

const SEO = ({ seo = {} }) => {
  const { strapiGlobal } = useStaticQuery(query);
  const { defaultSeo, siteName, favicon } = strapiGlobal;
  ...
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

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
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

...
render={(data) => (
...
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

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
                }
              }
            }
          }
        }
      }
    }
Enter fullscreen mode Exit fullscreen mode

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

<ArticlesComponent articles={data.allStrapiArticle.edges} />
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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
              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,
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  • 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
              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,
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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

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.

Discussion (0)