Strapi blog logo
  • Product

      Why Strapi?Content ArchitectureRoadmapIntegrationsTry live demo
      OverviewContent Types BuilderCustomizable APIMedia LibraryRoles & Permissions
      Discover Strapi Enterprise EditionDiscover our partners
  • Pricing

  • Solutions

      Static WebsitesMobile ApplicationsCorporate websitesEditorial SitesEcommerce
      Delivery HeroL'EquipeSociete GeneralePixelDustBanco International
      Discover all our user stories
  • Community

      CommunityWrite for the communityWall of LoveStrapi Conf 2021
      SlackGitHubYoutubeCommunity Forum
      Meet the Strapi Community StarsDiscover the Strapi Showcase
  • Resources

      BlogStartersNewsroomSupport
      Strapi AcademyTutorialsVideos GuidesWebinars
      The Guide to Headless CMS Strapi Community Forum
  • Docs

      Getting StartedContent APIConfigurationInstallationDeploymentMigration
      Getting StartedContent ManagerContent-Types BuilderUsers, Roles & PermissionsPlugins
      Developer DocumentationStrapi User Guide

Looking for our logo ?

Logo Brand download
Download Logo Pack
See more Strapi assets
Strapi blog logo
  • Product

    Product

    • Why Strapi?
    • Content Architecture
    • Roadmap
    • Integrations
    • Try live demo

    Features

    • Overview
    • Content Types Builder
    • Customizable API
    • Media Library
    • Roles & Permissions
    • Discover Strapi Enterprise Edition
    • Discover our partners
    Features cover

    Unlock the full potential of content management

    See all features
    Strapi Enterprise Edition

    Discover the advanced features included in Strapi Enterprise Edition.

    Get Started
  • Pricing
  • Solutions

    Solutions

    • Static Websites
    • Mobile Applications
    • Corporate websites
    • Editorial Sites
    • Ecommerce

    Stories

    • Delivery Hero
    • L'Equipe
    • Societe Generale
    • PixelDust
    • Banco International
    • Discover all our user stories
    Delivery Hero team

    Delivery Hero manages their partner portal with Strapi.

    Read their story
    turn 10 studios website

    How 1minus1 delivered a creative website for Turn10 Studios 25% faster with Strapi

    Discover their story
  • Community

    Community

    • Community
    • Write for the community
    • Wall of Love
    • Strapi Conf 2021

    Resources

    • Slack
    • GitHub
    • Youtube
    • Community Forum
    • Meet the Strapi Community Stars
    • Discover the Strapi Showcase
    Strapi Conf

    The first Strapi Global User Conference.

    Register now
    Write for the community

    Contribute on educational content for the community

    Discover the program
  • Resources

    Resources

    • Blog
    • Starters
    • Newsroom
    • Support

    Learning

    • Strapi Academy
    • Tutorials
    • Videos Guides
    • Webinars
    • The Guide to Headless CMS
    • Strapi Community Forum
    Introducing Strapi Academy

    Everything you need to know to master Strapi.

    Go to the academy
    Strapi Repository on GitHub

    Get started with the Strapi repository

    Go to repository
  • Docs

    Developers

    • Getting Started
    • Content API
    • Configuration
    • Installation
    • Deployment
    • Migration

    User Guide

    • Getting Started
    • Content Manager
    • Content-Types Builder
    • Users, Roles & Permissions
    • Plugins
    • Developer Documentation
    • Strapi User Guide
    Install Strapi

    Install Strapi locally or wherever you need.

    Get Started
    Migration Guides Strapi

    Using a previous version of Strapi? Migrate to the latest.

    Read Guides
Get Started
Back to articles

Build a blog with React, Strapi and Apollo

build-a-blog-with-react-strapi-and-apollo
  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email

Maxime Castres

January 8, 2020

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 React developers who wants to build a simple blog with Strapi!

What is React?

React is an open-source, front end, JavaScript library for building user interfaces or UI components. It is maintained by Facebook and a community of individual developers and companies. React can be used as a base in the development of single-page or mobile applications.

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

React 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 React for the frontend The source code is available on GitHub.

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

  • Add the Graphql plugin to Strapi by running the following command.
cd backend
yarn strapi install graphql

It will allow you to request your content-types using graphql.

Nice! Now that Strapi is ready, you are going to create your React 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

You'll need to restart your server.

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 inside the ApolloProvider and import your index.css file in the ./src/index.js:
import React from "react";
import ReactDOM from "react-dom";
import { ApolloProvider } from "@apollo/react-hooks";
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

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, slug }) => {
  const { data, loading, error } = useQuery(query, {
    variables: { slug: slug }
  });

  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 {
      slug
      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.slug}>
                          <Link
                            to={`/category/${category.slug}`}
                            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/article/articles.js file containing the following code:
import gql from "graphql-tag";

const ARTICLES_QUERY = gql`
  query Articles {
    articles {
      slug
      title
      category {
        slug
        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.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__${article.slug}`} />;
            })}
          </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 }) => {
  const imageUrl =
    process.env.NODE_ENV !== "development"
      ? article.image.url
      : process.env.REACT_APP_BACKEND_URL + article.image.url;
  return (
    <Link to={`/article/${article.slug}`} className="uk-link-reset">
      <div className="uk-card uk-card-muted">
        <div className="uk-card-media-top">
          <img
            src={imageUrl}
            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} slug={id}>
      {({ data: { articles } }) => {

        if (articles.length) {
          const imageUrl =
            process.env.NODE_ENV !== "development"
              ? articles[0].image.url
              : process.env.REACT_APP_BACKEND_URL + articles[0].image.url;

          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={imageUrl}
                data-srcset={imageUrl}
                data-uk-img
              >
                <h1>{articles[0].title}</h1>
              </div>

              <div className="uk-section">
                <div className="uk-container uk-container-small">
                  <ReactMarkdown source={articles[0].content} />
                  <p>
                    <Moment format="MMM Do YYYY">{articles[0].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 ARTICLES_QUERY = gql`
  query Articles {
    articles {
      slug
      title
      category {
        slug
        name
      }
      image {
        url
      }
    }
  }
`;

export default ARTICLES_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} slug={id}>
      {({ data: { categories } }) => {

        if (categories.length) {
          return (
            <div>
              <div className="uk-section">
                <div className="uk-container uk-container-large">
                  <h1>{categories[0].name}</h1>
                  <Articles articles={categories[0].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($slug: String!){
    categories(where: {slug: $slug}) {
      name
      articles {
        slug
        title
        content
        image {
          url
        }
        category {
          slug
          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.

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.

This article was updated on February 15th 2021

  • Try live demo
  • Starters
  • Become a Strapi expert
  • Find help in our Forum
  • Strapi on Youtube
  • Try Enterprise Edition
  • Share on facebook
  • Share on linkedin
  • Share on twitter
  • Share by email

Unleash content.

Read the docs
Get Started

Strapi is the most popular open-source Headless CMS. Strapi gives developers the freedom to use their favorite tools and frameworks while allowing editors to easily manage their content and distribute it anywhere.

Product

  • Why Strapi?
  • Content Architecture
  • Features
  • Enterprise Edition
  • Partner Program
  • Roadmap
  • Support
  • Integrations
  • Try live demo
  • Changelog

Resources

  • How to get started
  • Meet the community
  • Tutorials
  • API documentation
  • GitHub repository
  • Starters
  • Strapi vs Wordpress
  • The Guide to headless CMS

Integrations

  • Gatsby CMS
  • React CMS
  • Vue.js CMS
  • Nuxt.js CMS
  • Next.js CMS
  • Angular CMS
  • Gridsome CMS
  • Jekyll CMS
  • 11ty CMS
  • Svelte CMS
  • Sapper CMS
  • Ruby CMS
  • Python CMS

Company

  • About us
  • Blog
  • Careers
  • Contact
  • Newsroom
  • © 2021, Strapi
  • LicenseTermsPrivacy