Product
Resources
Maxime Castres
January 8, 2020
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!
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.
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:
You may want to directly try the result of this tutorial. Well, we made a starter out of it so give it a try!
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.
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.
take blog-strapi
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.
yarn create strapi-app backend --quickstart --template https://github.com/strapi/strapi-template-blog
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.
Feel free to modify all this, however, we will be satisfied with that for the tutorial.
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.
The easiest part has been completed, let's get our hands dirty developing our blog!
React setup
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
You are going to give a better structure to your React application! But first, let's install react-router-dom
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
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
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>
...
./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
yarn add apollo-boost @apollo/react-hooks graphql react-apollo
./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 !
.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.
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
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!
./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
./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
./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
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.
This container will wrap a component that will display all your articles
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
./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:
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;
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.
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;
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:
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.
./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!
./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
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!
You may want to separate your article depending on the categories! Let's create a page for each category then:
./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;
./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.
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.
Huge congrats, you successfully achieved this tutorial. I hope you enjoyed it!
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.
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