Using a headless CMS with React
Tags: react, tutorial
Published: 2019-12-20 18:14:28

---
title: Using a headless CMS with React
published: true
date: 2019-12-17 14:00:03 UTC
tags: react,tutorial
canonical_url: https://blog.logrocket.com/using-a-headless-cms-with-react/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/rg59nzgzaqgd6pycujks.jpeg
---
**Written by [Ovie Okeh](https://blog.logrocket.com/author/ovieokeh/)**✏️

I would like to build a blog on my personal React website, but I have some questions. Where do I store the images and content for each post? How do I store the content? Sure, I could hardcode each post, upload the images to a CDN and manually link to it, but would that be able to scale to 100 posts? 200?

What I need is a content management system (CMS) like WordPress, but I’m quite happy with my React site and I don’t want to switch. Oh, I know — I’ve heard some colleagues talking about headless content management systems. Could that be what I need?

…

OK, [I did some research](https://www.storyblok.com/tp/headless-cms-explained) and discovered that a headless CMS is exactly what I need. It gives me an interface to write my blog posts along with the ability to deliver it anywhere I want. That sounds good, but which one do I go for? I know there are a lot of options out there.

I asked around and [Contentful](https://www.contentful.com/) was recommended a lot, so I guess it’s worth a try. This is what I plan to do:

- Set up Contentful to host my blog posts
- Upload and publish some posts
- Pull in my blog posts to my React app
- Serve it to my imaginary readers

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Setting up Contentful

Hmm… So I read a bit more about Contentful on the official website, and it claims that it’s not a traditional headless CMS. It’s a “Content Infrastructure” and apparently will give me more flexibility on how to structure my content.

If you ask me, though, I think it’s just a variant of a headless CMS because it satisfies the criteria for being one. If it allows you to write once and deliver anywhere, then it’s a headless CMS to me. [🤷](https://s.w.org/images/core/emoji/12.0.0-1/72x72/1f937.png)

Anyway, I signed up for a free account and it turns out that setting it up was really easy. After I clicked on signup, I was greeted with this page:

![Contentful Get Started Page](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/contentful-get-started-page.png?resize=730%2C400&ssl=1)<figcaption id="caption-attachment-11061">I do both, so what should I select? [🤔](https://s.w.org/images/core/emoji/12.0.0-1/72x72/1f914.png)  

I decided to **Explore content modeling** , so I clicked on the left button and a sample project was created for me. I’m an explorer, though, so I decided to create my own project from scratch. Projects are called spaces in Contentful, btw.

I clicked on the sidebar to my left and clicked on the **+ Create space** button, which opened the modal below:

![Space Type Modal](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/space-type-modal.png?resize=730%2C398&ssl=1)<figcaption id="caption-attachment-11063">Free stuff never hurt anyone.</figcaption>

I then had to select a name for my new project, so I went with something creative because I’m just drowning in creativity.

![Choose A Name Modal](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/choose-name-modal.png?resize=730%2C399&ssl=1)<figcaption id="caption-attachment-11065">Pick a better space name, I dare you.</figcaption>

And finally, to confirm that I indeed wanted a new space, I was presented with the last and final modal to conquer.

![Confirmation Modal](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/confirmation-modal.png?resize=730%2C397&ssl=1)

OK, I now have a new space created. It’s time to create my blog posts.

## Creating a blog post

Before I could create a blog post, I had to create something called a [Content Model](https://www.contentful.com/developers/docs/concepts/data-model/), which is simply the structure of how a type of content should look. I’m choosing to think of this as a schema for my content.

I had to come up with the structure of how the posts should look, and thankfully, it was pretty easy. It was as simple as writing down what data each post needs and the type of that data. In my case, these are the following pieces of data required, along with the data type:

- **Title** – Short text
- **Slug** – Short text
- **Description** – Long text
- **Featured Image** – An image
- **Date** – Date & time
- **Body** – Long text

After writing down the required pieces of data, I went ahead and created my Content Model in Contentful. In the **my blog** space I just created, I clicked on **Content model** on the top navigation menu and clicked on **Add content type** in the following page.

A modal popped up, and I filled in the name for my new Content Model. I just called it “Blog Post” and started adding the fields I listed above. By the time I was done adding all the different fields, I had something similar to the below:

![Content Model Modal](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/content-model-modal.png?resize=730%2C364&ssl=1)<figcaption id="caption-attachment-11069">Easy peasy.</figcaption>

Now that I had my blog post content model (or schema, if you prefer) set up, I decided it was time to add the actual blog posts that I would pull into my React app.

Still in the **my blog** space, I clicked on **Content** on the top navigation menu and clicked on **Add Blog Post**. If you’re following along and you named your content model something else, **Add Blog Post** might be something different.

Anyway, clicking on that button took me to a page where I could write and edit my blog posts like so:

![Write And Edit Blog Post Page](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/write-edit-blog-post-page.png?resize=730%2C364&ssl=1)

This is why I needed a CMS in the first place — a place to write and edit my blog posts so that I could deliver them anywhere I like. I went ahead and added three dummy posts so that I would have something to pull into my React app.

Here’s how my list of blog posts looked by the time I was done:

![List Of Blog Posts](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/blog-post-list.png?resize=730%2C364&ssl=1)

OK, this is has been going well, and I feel it’s time to recap what I’ve learned so far:

- A headless content management system allows me to create my content once and deliver it anywhere I like
- Contentful is one such CMS, with more advanced functionality like well-structured schemas for my content
- I can create and edit my content in a variety of formats, including Markdown and Rich Text
- Contentful also provides a CDN for storing and hosting any media I choose to upload in my blog posts

## Integrating Contentful into a React app

Before I could integrate Contentful into my app, I actually had to create the app first. I wanted my blog to look exactly like the one below.

{% codesandbox hx0de %}

So what are the different components for this app?

- An `App.jsx` component to handle routing to the different pages
- A `Posts.jsx` component to display the list of posts on the site
- A `SinglePost.jsx` component to display a single post

Well, it turns out not a whole lot. Of course, if you have your own personal site and are looking to follow this tutorial, you might have many more components, but for this case, that’s all I needed.

## Building the app

I ran the following scripts to set up my project and install the required dependencies:

```jsx
mkdir react-contentful && cd react-contentful
npm init -y
npm i --save react react-dom react-router-dom react-markdown history contentful
npm i --save-dev parcel-bundler less
```

There are two particularly important packages I just installed: `react-markdown` and `contentful`.

[`react-markdown`](https://github.com/rexxars/react-markdown) allows me to parse Markdown content into HTML tags. I needed it because I’m storing my post content as “Long text” in Contentful, and this means my post body will be in Markdown.

[`contentful`](https://www.npmjs.com/package/contentful) is the official Node package from Contentful that will allow me to interact with its API. I needed it to retrieve my content from Contentful. Every other package is self-explanatory.

## Creating my files

After installing all the required dependencies, I went ahead and created the different files and folders I needed for this project. I’m going to leave out the content of some of the files from this tutorial, but I’ll add links so you can copy them and follow along.

- Run this script to create all the required folders:

```jsx
mkdir public src src/components src/custom-hooks src/components/{posts,single-post}
```

- Run this script to create all the required files:

```jsx
touch public/index.html public/index.css src/{index,contentful}.js
```

- Run this script to create all the components:

```jsx
touch src/components/App.jsx src/components/helpers.js src/components/posts/Posts.jsx src/components/posts/Posts.less src/components/single-post/SinglePost.jsx src/components/single-post/SinglePost.less
```

- Run this script to create all the custom Hooks:

```jsx
touch src/custom-hooks/{index,usePosts,useSinglePost}.js
```

I will not go through the code for the following files because they’re not essential to this tutorial:

- [`public/index.html`](https://github.com/ovieokeh/react-contentful/blob/master/public/index.html)
- [`public/index.css`](https://github.com/ovieokeh/react-contentful/blob/master/public/index.css)
- [`src/index.js`](https://github.com/ovieokeh/react-contentful/blob/master/src/index.js)
- [`src/components/posts/Posts.less`](https://github.com/ovieokeh/react-contentful/blob/master/src/components/posts/Posts.less)
- [`src/components/posts/SinglePost.less`](https://github.com/ovieokeh/react-contentful/blob/master/src/components/single-post/SinglePost.less)
- [`src/components/helpers.js`](https://github.com/ovieokeh/react-contentful/blob/master/src/components/helpers.js)
- [`src/custom-hooks/index.js`](https://github.com/ovieokeh/react-contentful/blob/master/src/custom-hooks/index.js)

## Populating the files

Now that I had my project structure ready with all the required files and folders, I started writing code, and I’ll start with the most essential pieces first.

### `src/contentful.js`

```jsx
const client = require('contentful').createClient({
  space: '<my_space_id>',
  accessToken: '<my_access_token>'
})

const getBlogPosts = () => client.getEntries().then(response => response.items)

const getSinglePost = slug =>
  client
    .getEntries({
      'fields.slug': slug,
      content_type: 'blogPost'
    })
    .then(response => response.items)

export { getBlogPosts, getSinglePost }
```

So I started with the code that interacts with Contentful to retrieve my blog posts.

I wanted to query Contentful for my content, so I went through the [`contentful` package docs](%22https://www.npmjs.com/package/contentful#your-first-request) and discovered that I needed to import the package and pass it a config object containing a space ID and my access token.

Getting this information was trivial and all I had to do was follow the instructions on the [Contentful docs.](https://www.contentful.com/developers/docs/references/authentication/)

After getting my space ID and my access token, I required the `contentful` package and called the `createClient` method with a config object containing my credentials. This gave me an object, `client`, that allowed me to interact with Contentful.

So to recap, I wanted to retrieve:

- All my blog posts
- A single blog post by its slug

For retrieving all my blog posts, I created a function, `getBlogPosts`, that did this for me. Inside this function, I called `client.getEntries()`, which returns a Promise that eventually resolves to a `response` object containing `items`, which is my array of blog posts.

For retrieving a single blog post, I created a function called `getSinglePost`, which takes in a “slug” argument and queries Contentful for any post with that slug. Remember that “slug” is one of the fields I created in my blog post content model, and that’s why I can reference it in my query.

Inside the `getSinglePost` function, I called `client.getEntries()` again, but this time, I passed a query object specifying that I wanted any content that:

- Has a slug matching the “slug” argument
- Is a blog post

Then, at the end of the file, I exported both functions so I could make use of them in other files. I created the custom Hooks next.

### `custom-hooks/usePosts.js`

```jsx
import { useEffect, useState } from 'react'

import { getBlogPosts } from '../contentful'

const promise = getBlogPosts()

export default function usePosts() {
  const [posts, setPosts] = useState([])
  const [isLoading, setLoading] = useState(true)

  useEffect(() => {
    promise.then(blogPosts => {
      setPosts(blogPosts)
      setLoading(false)
    })
  }, [])

  return [posts, isLoading]
}
```

The `usePosts` Hook allows me to retrieve my blog posts from Contentful from the `Posts.jsx` component.

I imported three modules into this file:

1. `useEffect`: I needed this to update the custom Hook’s state
2. `useState`: I needed this to store the list of blog posts as well as the current loading state
3. `getBlogPosts`: This function allowed me to query Contentful for my blog posts

After importing all the required modules into this file, I kicked off the call to fetch my blog posts by calling the `getBlogPosts()` function. This returns a Promise, which I stored in the `promise` variable.

Inside the `usePosts()` Hook, I initialized two state variables:

1. `posts`, to hold the list of blog posts
2. `isLoading`, to hold the current loading state for the blog posts fetch request

Then, in the `useEffect` call, I resolved the Promise I created earlier and then updated the `posts` state variable with the new blog posts data. I also set the loading state to be false after this was done.

At the end of this Hook, I returned an array containing the `posts` and the `isLoading` variables.

### `custom-hooks/useSinglePost.js`

```jsx
import { useEffect, useState } from 'react'

import { getSinglePost } from '../contentful'

export default function useSinglePost(slug) {
  const promise = getSinglePost(slug)

  const [post, setPost] = useState(null)
  const [isLoading, setLoading] = useState(true)

  useEffect(() => {
    promise.then(result => {
      setPost(result[0].fields)
      setLoading(false)
    })
  }, [])

  return [post, isLoading]
}
```

The `useSinglePost` custom Hook is very similar to the `usePosts` Hook, with a few minor exceptions.

Unlike `usePosts`, where I kicked off the call to `getBlogPosts` outside of the Hook, I made the call (but to `getSinglePost()`) inside the `useSinglePost` Hook. I did this because I wanted to pass in the “slug” argument to the `getSinglePost` function, and I couldn’t do that if it was invoked outside the custom Hook.

Moving on, I also had the same state variables to hold the single post being retrieved, as well as the loading state for the request.

In the `useEffect` call, I resolved the Promise and updated the state variables as appropriate.

I also returned an array containing the `post` and the `isLoading` state variables at the end.

### `components/App.jsx`

```jsx
import React from 'react'
import { Router, Switch, Route } from 'react-router-dom'
import { createBrowserHistory } from 'history'

import Posts from './posts/Posts'
import SinglePost from './single-post/SinglePost'

export default function App() {
  return (
    <Router history={createBrowserHistory()}>
      <Switch>
        <Route path="/" exact component={Posts} />
        <Route path="/:id" component={SinglePost} />
      </Switch>
    </Router>
  )
}
```

`App.jsx` is the root component responsible for routing the user to the correct page.

I imported a bunch of required dependencies. I also needed a refresher on how React Router works, [so I went through this short article.](https://blog.pshrmn.com/simple-react-router-v4-tutorial/)

### `components/posts/Posts.jsx`

So now that I had all my custom Hooks and querying functions setup, I wanted to retrieve all my blog posts and display them in a grid, like so:

![Article Preview Grid On Blog Homepage](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/article-preview-grid.png?resize=730%2C330&ssl=1)<figcaption id="caption-attachment-11073">This design deserves an Awwward.</figcaption>

I started off with a bunch of dependency imports, among which is the `usePosts` custom Hook for fetching all my blog posts from Contentful. I also created a nice little helper called `readableDate`, which helped me parse the date the article was published into a user-friendly format.

```jsx
import React from 'react'
import { Link } from 'react-router-dom'

import { usePosts } from '../../custom-hooks/'
import { readableDate } from '../helpers'
import './Posts.less'

...continued below...
```

I created the component next. It’s a simple functional component without any state variables to manage or keep track of.

Right at the beginning, I made use of the `usePosts` Hook to get my posts and the loading state. Then I defined a function, `renderPosts`, to iterate over the list of blog posts and returned a bunch of JSX for each post.

Inside this function, I checked the loading state first. If the request is still loading, it returns the loading message and ends execution there. Otherwise, it maps over the array of posts, and for each one, returns a `<Link />` element.

This `Link` element will redirect my readers to the slug of whatever post they click on. Inside this link element, I also rendered some important information like the featured image of the article, the date it was published, the title, and a short description.

Finally, in the return statement of the `Posts` component, I called the `renderPosts()` function.

```jsx
...continuation...
export default function Posts() {
  const [posts, isLoading] = usePosts()

  const renderPosts = () => {
    if (isLoading) return <p>Loading...</p>

    return posts.map(post => (
      <Link
        className="posts__post"
        key={post.fields.slug}
        to={post.fields.slug}
      >
        <div className="posts__post__img__container">
          <img
            className="posts__post__img__container__img"
            src={post.fields.featuredImage.fields.file.url}
            alt={post.fields.title}
          />
        </div>

        <small>{readableDate(post.fields.date)}</small>
        <h3>{post.fields.title}</h3>
        <p>{post.fields.description}</p>
      </Link>
    ))
  }

  return (
    <div className="posts__container">
      <h2>Articles</h2>

      <div className="posts">{renderPosts()}</div>
    </div>
  )
}
```

So, to recap, here’s what I did in this component:

- I called the `usePosts()` custom Hook. This returns two variables, `posts` and `isLoading`. `posts` is either going to be empty or contain the list of blog posts on my Contentful space. `isLoading` is either true or false, depending on whether the request to fetch the blog posts is still pending
- I defined a `renderPosts()` function that will either render a loading message to the DOM or render my blog posts. It checks the `isLoading` variable to determine whether the blog posts are ready and then renders the appropriate content to the DOM
- In the return statement, I returned a bunch of JSX along and called `renderPosts()`

Moving on to the next component.

### `components/single-post/SinglePost.jsx`

I also needed to render single blog posts, and to do this, I needed a `SinglePost` component, which should look like this:

![Example Of A Single Post](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/single-post-example.png?resize=730%2C508&ssl=1)<figcaption id="caption-attachment-11075">Interesting post.</figcaption>

Again, I started off with a bunch of dependency imports, as usual:

```jsx
import React from 'react'
import { Link, useParams } from 'react-router-dom'
import MD from 'react-markdown'

import { useSinglePost } from '../../custom-hooks'
import { readableDate } from '../helpers'
import './SinglePost.less'
```

There are a couple of new, unfamiliar imports here:

- [`useParams`](https://reacttraining.com/blog/react-router-v5-1/#useparams): This will allow me to read the dynamic route parameters from React Router
- [`MD`](https://github.com/rexxars/react-markdown): This will help me convert my Markdown content to HTML and render it

Apart from the new ones, I also imported the `useSinglePost` custom Hook as well as the `readableDate` helper.

Next, I created the actual component.

```jsx
...continued...
export default function SinglePost() {
  const { id } = useParams()
  const [post, isLoading] = useSinglePost(id)

  const renderPost = () => {
    if (isLoading) return <p>Loading...</p>

    return (
      <>
        <div className="post__intro">
          <h2 className="post__intro__title">{post.title}</h2>
          <small className="post__intro__date">{readableDate(post.date)}</small>
          <p className="post__intro__desc">{post.description}</p>

          <img
            className="post__intro__img"
            src={post.featuredImage.fields.file.url}
            alt={post.title}
          />
        </div>

        <div className="post__body">
          <MD source={post.body} />
        </div>
      </>
    )
  }
...continued below...
```

Before I continue, I would like to talk a little bit about how `useParams` works. In `App.jsx`, I had the following snippet of code:

```jsx
<Route path="/:id" component={SinglePost} />
```

This simply routes any request that matches the URL pattern passed to `path` to the `SinglePost` component. React Router also passes some additional props to the `SinglePost` component. One of these props is a `params` object that contains all the parameters in the path URL.

In this case, `params` would contain `id` as one of the parameters because I explicitly specified `id` in the path URL for this particular route. So, if I navigated to a URL like `localhost:3000/contentful-rules`, `params` would look like this:

```jsx
{
  id: 'contentful-rules'
}
```

This is also where `useParams` comes into play. It will allow me to query the `params` object without having to destructure it from the component’s props. I now have a way to grab whatever slug is in the current URL.

OK, back to the component. Now that I had a way to get the slug of whichever article was clicked on, I was now able to pass the slug down to the `useSinglePost` custom Hook, and I was able to get back the post with that slug as well as the loading state for the request to fetch the post.

After getting the post object and the loading state from the `useSinglePost` Hook, I defined a `renderPost` function that will either render a loading message to the DOM or the actual post, depending on the loading state.

Also notice that towards the end of the snippet, I have this line of code:

```jsx
<MD source={post.body} />
```

This is the React Markdown component that I need to parse my Markdown post body into actual HTML that the browser recognizes.

```jsx
...continued...

  return (
    <div className="post">
      <Link className="post__back" to="/">
        {'< Back'}
      </Link>

      {renderPost()}
    </div>
  )
}
```

Finally, I have the return statement to render my data from this component. I added a link back to the homepage so that my users would be able to go back to the homepage easily. After the link, I simply called the `renderPost()` function to render the post to the DOM.

To recap, here’s what I did in this component.

- I called the `useSinglePost()` custom Hook. This returns two variables, `post` and `isLoading`. `post` will either be null or an object containing the post data. `isLoading` is either true or false, depending on whether the request to fetch the post is still pending
- I defined a `renderPost()` function that will either render a loading message to the DOM or render the blog post. It checks the `isLoading` variable to determine whether the blog post is ready and then renders the appropriate content to the DOM
- In the return statement, I returned a bunch of JSX along and called `renderPost()`

## Putting it all together

After writing the code for all the components and adding the appropriate styling, I decided to run my project to see if it all worked. In my `package.json`, I added the following scripts:

```jsx
"scripts": {
    "start": "parcel public/index.html",
    "build": "parcel build public/index.html --out-dir build --no-source-maps"
  },
```

When I ran `npm run start` in my terminal, Parcel built my React app for me and served it over port 1234. Navigating to `http://localhost:1234` on my browser displayed my app in all its glory, along with the blog posts.

I tried clicking on a single blog post and I was redirected to a page where I was able to read that blog post, so it seems that my little experiment with React and Contentful worked as I wanted it to.

![Site Preview](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2019/12/site-preview.gif?resize=600%2C323&ssl=1)

I’m fully aware that this is not the best way to build something as simple as a static blog, though. There are much better options, like [Next.js](https://nextjs.org/) and [Gatsby.js](https://www.gatsbyjs.org/), that would make this process a whole lot easier and would actually result in a faster, more accessible blog by default.

But if your use case is simply to get your content from Contentful into your React app, then this guide should be helpful to you.

* * *
##Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-frontend-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/1k0ootxeqtruge3yrpzx.png)

[LogRocket](https://www2.logrocket.com/react-frontend-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free](https://www2.logrocket.com/react-frontend-monitoring).
* * *
The post [Using a headless CMS with React](https://blog.logrocket.com/using-a-headless-cms-with-react/) appeared first on [LogRocket Blog](https://blog.logrocket.com).