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

This one if for React developers who wants to build a simple blog with Strapi!

React blog

Webinar

We made a webinar on February 20 where I did this tutorial live on Livestorm

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 blog website using Strapi as the backend, React for the frontend, and Apollo for requesting the Strapi API with GraphQL.

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 Next.JS application.

Front-end setup

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

React setup

  • Create a React frontend server by running the following command:

yarn create react-app frontend

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

cd frontend
yarn dev

Front-end structure

You are going to give a better structure to your React application! But first, let's install react-router-dom

  • Install react-router-dom by running the following command:

yarn add react-router-dom

  • Remove everything inside your src folder

  • Create an index.js file containing the following code:

import React from "react";  
import ReactDOM from "react-dom";  
import App from "./containers/App";  
import { BrowserRouter as Router } from "react-router-dom";

ReactDOM.render(  
  <Router>
    <App />
  </Router>,
  document.getElementById("root")
);

What you are doing here is to wrap your App inside Router. But what/where is your App ?

Well we're going to create a containers folder that will contains your App

  • Create a containers/App folder and a index.js file inside containing the following code:
import React from "react";

function App() {  
  return <div className="App" />;
}

export default App;  

To make your blog look pretty, we will use a popular CSS framework for styling: UiKit and Apollo to query Strapi with GraphQL.

UIkit setup

  • Add the following line in your public/index.html in order to use UIkit from their CDN
...
<title>React App</title>  
<link  
      rel="stylesheet"
      href="https://fonts.googleapis.com/css?family=Staatliches"
    />
    <link
      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"></script>
    <script src="https://cdn.jsdelivr.net/npm/uikit@3.2.3/dist/js/uikit-icons.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.2.0/js/uikit.js"></script>
...
  • Create an ./src/index.css file containing the following style:
a {  
  text-decoration: none;
}

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

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

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

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

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

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

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

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

Apollo setup

  • Install all the necessary dependencies for apollo by running the following command

yarn add apollo-boost @apollo/react-hooks graphql react-apollo

  • Create a ./src/utils/apolloClient.js file containing the following code:
import { ApolloClient } from "apollo-client";  
import { InMemoryCache } from "apollo-cache-inmemory";  
import { HttpLink } from "apollo-link-http";

const cache = new InMemoryCache();  
const link = new HttpLink({  
  uri: `${process.env.REACT_APP_BACKEND_URL}/graphql`
});
const client = new ApolloClient({  
  cache,
  link
});

export default client;  

Oh oh! It looks like you don't have any REACT_APP_BACKEND_URL environment variable yet !

  • Create a .env file in the root of your application containing the following line:

REACT_APP_BACKEND_URL="http://localhost:1337"

Note: You want Apollo to point to this address http://localhost:1337/graphql. That's the one where you'll be able to fetch your data from your Strapi server.

  • Wrap your App/index.js inside the ApolloProvider and import your index.css file:
import React from "react";  
import ReactDOM from "react-dom";  
import { ApolloProvider } from "react-apollo";  
import App from "./containers/App";  
import client from "./utils/apolloClient";  
import { BrowserRouter as Router } from "react-router-dom";  
import "./index.css";

ReactDOM.render(  
  <Router>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </Router>,
  document.getElementById("root")
);

Awesome! Your app should be ready now! You should have a blank page if everything went ok

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!

Create the Query component

You are going to use Apollo to fetch your data from different pages. We don't want you to rewrite the same code every time in your pages. This is why you are going to write a Query component that will be reusable!

  • Create a ./src/components/Query/index.js file containing the following code:
import React from "react";  
import { useQuery } from "@apollo/react-hooks";

const Query = ({ children, query, id }) => {  
  const { data, loading, error } = useQuery(query, {
    variables: { id: id }
  });

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {JSON.stringify(error)}</p>;
  return children({ data });
};

export default Query;  

We are using the useQuery hook to call your Strapi server at this address http://localhost:1337/graphql. We are sending an id if it exists (it will be necessary when you'll want to fetch just one article).

If the request is successful, you will return the child component with the retrieved data as prop.

Let's try it out by creating your navbar that will fetch all our categories but first let's create the GraphQL query

  • Create a ./src/queries/category/categories.js file containing the following code:
import gql from "graphql-tag";

const CATEGORIES_QUERY = gql`  
  query Categories {
    categories {
      id
      name
    }
  }
`;

export default CATEGORIES_QUERY;  

Let's use this query to display your categories inside your navbar

  • Create a ./src/components/Nav/index.js file containing the following code:
import React from "react";  
import Query from "../Query";  
import { Link } from "react-router-dom";

import CATEGORIES_QUERY from "../../queries/category/categories";

const Nav = () => {  
  return (
    <div>
      <Query query={CATEGORIES_QUERY} id={null}>
        {({ data: { categories } }) => {
          return (
            <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">
                    {categories.map((category, i) => {
                      return (
                        <li key={category.id}>
                          <Link
                            to={`/category/${category.id}`}
                            className="uk-link-reset"
                          >
                            {category.name}
                          </Link>
                        </li>
                      );
                    })}
                  </ul>
                </div>
              </nav>
            </div>
          );
        }}
      </Query>
    </div>
  );
};

export default Nav;  

Since we want our Nav to be in every pages of our application we are going to use it inside our App container

  • Import and declare your Nav component inside your containers/App/index.js
import React from "react";  
import Nav from "../../components/Nav";

function App() {  
  return (
    <div className="App">
      <Nav />
    </div>
  );
}

export default App;  

Great! You should now be able to see your brand new navbar containing your categories. But the links are not working right now. We'll work on this later in 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.

Create the Articles containers

This container will wrap a component that will display all your articles

  • Create a containers/Articles/index.js file containing the following code:
import React from "react";  
import Articles from "../../components/Articles";  
import Query from "../../components/Query";  
import ARTICLES_QUERY from "../../queries/article/articles";

const Home = () => {  
  return (
    <div>
      <div className="uk-section">
        <div className="uk-container uk-container-large">
          <h1>Strapi blog</h1>
          <Query query={ARTICLES_QUERY}>
            {({ data: { articles } }) => {
              return <Articles articles={articles} />;
            }}
          </Query>
        </div>
      </div>
    </div>
  );
};

export default Home;  

Let's write the query that fetch all your articles

  • Create a ./src/queries/articles/articles.js file containing the following code:
import gql from "graphql-tag";

const ARTICLES_QUERY = gql`  
  query Articles {
    articles {
      id
      title
      category {
        id
        name
      }
      image {
        url
      }
    }
  }
`;

export default ARTICLES_QUERY;  

Now we need to create an Articles component that will display all of your articles and an Card component that will display each of your article:

  • Create a components/Articles/index.js file containing the following:
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.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.id}`} />;
            })}
          </div>
        </div>
      </div>
    </div>
  );
};

export default Articles;  
  • Create a components/Card/index.js file containing the following code:
import React from "react";  
import { Link } from "react-router-dom";

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

export default Card;  

Everything is ready, we just need to import the Articles container inside the App container.

  • Import and declare your Articles container inside your containers/App/index.js:
import React from "react";

import Articles from "../Articles";  
import Nav from "../../components/Nav";

function App() {  
  return (
    <div className="App">
      <Nav />
      <Articles />
    </div>
  );
}

export default App;  

Strapi Blog NextJS homepage

Create the Article container

You can see that if you click on the article, there is nothing. Let's create the article container together! But first, you'll need two packages:

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

yarn add react-moment react-markdown moment

react-moment will give you the ability to display the publication date of your article, and react-markdown will be used to display the content of your article in markdown.

  • Create a ./containers/Article/index.js file containing the following:
import React from "react";  
import { useParams } from "react-router";  
import Query from "../../components/Query";  
import ReactMarkdown from "react-markdown";  
import Moment from "react-moment";

import ARTICLE_QUERY from "../../queries/article/article";

const Article = () => {  
  let { id } = useParams();
  return (
    <Query query={ARTICLE_QUERY} id={id}>
      {({ data: { article } }) => {
        return (
          <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={process.env.REACT_APP_BACKEND_URL + article.image.url}
              data-srcset={
                process.env.REACT_APP_BACKEND_URL + article.image.url
              }
              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>
        );
      }}
    </Query>
  );
};

export default Article;  

Let's write the query for just one article now!

  • Create a ./src/queries/article/article.js containing the following code:
import gql from "graphql-tag";

const ARTICLE_QUERY = gql`  
  query Articles($id: ID!) {
    article(id: $id) {
      id
      title
      content
      image {
        url
      }
      category {
        id
        name
      }
      published_at
    }
  }
`;

export default ARTICLE_QUERY;  

Article page is ready, we just need to add this new Article container inside the App container. You are going to use the Switch and Route components from react-router-dom in order to establish a routing system for your article page

  • Import and declare your Article container inside your containers/App/index.js:
import React from "react";

import { Switch, Route } from "react-router-dom";

import Nav from "../../components/Nav";  
import Articles from "../Articles";  
import Article from "../Article";

function App() {  
  return (
    <div className="App">
      <Nav />
      <Switch>
        <Route path="/" component={Articles} exact />
        <Route path="/article/:id" component={Article} exact />
      </Switch>
    </div>
  );
}

export default App;  

Great! You should be able to get your article now!

Article page

Categories

You may want to separate your article depending on the categories!
Let's create a page for each category then:

  • Create a ./containers/Category/index.js file containing the following:
import React from "react";  
import { useParams } from "react-router";  
import Articles from "../../components/Articles";  
import Query from "../../components/Query";  
import CATEGORY_ARTICLES_QUERY from "../../queries/category/articles";

const Category = () => {  
  let { id } = useParams();

  return (
    <Query query={CATEGORY_ARTICLES_QUERY} id={id}>
      {({ data: { category } }) => {
        return (
          <div>
            <div className="uk-section">
              <div className="uk-container uk-container-large">
                <h1>{category.name}</h1>
                <Articles articles={category.articles} />
              </div>
            </div>
          </div>
        );
      }}
    </Query>
  );
};

export default Category;  
  • Create a ./src/queries/category/articles.js file containing the following:
import gql from 'graphql-tag';

const CATEGORY_ARTICLES_QUERY = gql`  
  query Category($id: ID!){
    category(id: $id) {
      name
      articles {
           id
        title
        content
        image {
          url
        }
        category {
          id
          name
        }
      }
    }
  }
`;

export default CATEGORY_ARTICLES_QUERY;  

Category page is ready, we just need to add this new container inside the App container.

  • Import and declare your Category container inside your containers/App/index.js:
import React from "react";

import { Switch, Route } from "react-router-dom";

import Nav from "../../components/Nav";  
import Articles from "../Articles";  
import Article from "../Article";  
import Category from "../Category";

function App() {  
  return (
    <div className="App">
      <Nav />
      <Switch>
        <Route path="/" component={Articles} exact />
        <Route path="/article/:id" component={Article} exact />
        <Route path="/category/:id" component={Category} exact />
      </Switch>
    </div>
  );
}

export default App;  

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

Category List

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.

If you are interested in improving this tutorial, feel free to join our slack channel here and contact me @Maxime Castres.

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!