Adebola's blog

Adebola's blog

Using WordPress as a Headless CMS With React

Using WordPress as a Headless CMS With React

React), the frontend framework created at Facebook in 2013, with over 165k GitHub stars is now one of the most loved Frontend frameworks used by JavaScript developers. React is a powerful framework that allows developers create re-usable code blocks called components, use the Virtual DOM to update DOM elements - making web apps more performant and has a huge collection of libraries to make the lives of developers easier.

WordPress on the other hand was created 10years before the first version of React was released and this powerful, feature rich framework is still alive and well today. WordPress still continues to power over 40% of the web and it won’t be going anywhere soon.

In this article, we’ll combine the power of both frameworks to create an application using React on the FrontEnd with WordPress as the backend - or more correctly - as the Content Management System (CMS) that powers our App.

Prerequisites

To follow this tutorial, you’ll need basic knowledge of

  • JavaScript, specifically JSX
  • Command Line
  • Local by Flywheel
  • NPM
  • Node
  • Postman (optional)

Setup the backend

WordPress Rest API

First, we’ll setup the backend with WordPress. For this example, we’ll be using Local by Flywheel. If you need help setting up Local on your machine, you can refer to this blog post by Tobi.

Using Postman, make a request to http://YOUR_SITE_NAME.local/wp-json/wp/v2/posts. You should get a response like below back.

Once you confirm that the POSTS endpoint is working, we’re good to go!

You can add additional POSTS, create custom post types etc on the backend using the WordPress GUI.

Setting up the FrontEnd

We’ll be building the FrontEnd of this application using Next.JS and TailwindCSS (my all-time favourite CSS framework). Next JS comes with image optimization, routing, serverless functions and more out of the box. It’s no surprise the project has over 64k GitHub stars. TailwindCSS is a utility first framework that provides low level classes for you to write CSS within your HTML.

Thankfully, we do not need to start from scratch. NextJS has a simple CLI shortcut that will generate a NextJS + TailwindCSS boilerplate for us.

To run the command below, make sure you’ve Node and NPM set up on your machine.

yarn create next-app --example with-tailwindcss wordpress-react-app && cd wordpress-react-app

The wordpress-react-app line at the end of the command above generates the example files inside a folder called wordpress-react-app.

Once the installation is done, head into the root directory of the project. Create a folder called lib > constants.js. In your constants.js file, add the following piece of code. Replace our URL with the URL of your site.

export const POSTS_API_URL = "http://YOUR_SITE_NAME.local/wp-json/wp/v2/posts"
export const AUTHORS_API_URL = "http://YOUR_SITE_NAME.local/wp-json/wp/v2/users"
export const MEDIA_API_URL = "http://YOUR_SITE_NAME.local/wp-json/wp/v2/media"

Getting and Displaying Posts on the FrontEnd

To make requests to our backend WordPress server, we can use the JavaScript Fetch API or Axios. Personally, I prefer using axios as I think the syntax is much cleaner.

We also need a HTML Parser to help Parse the HTML tags returned from the WordPress server.

Let’s install both packages.

yarn add axios html-react-parser

To make our code a little cleaner, we’ll also create some helper methods that handle requests to our server. In your lib folder, create an utils.js file. In this file, we’ll create methods that handle getting the post, author and featured image from our server.

WordPress by default exposes a /post/[id] endpoint that gives us information about a post. This endpoint will only give us the authorId and featuedImageId for a post and we would need to make additional requests to the /author/[id] and /media/[id] endpoints to get additional data about a post’s author and featured image.

Add the following block of code.

import axios from 'axios';
import { POSTS_API_URL, AUTHORS_API_URL, MEDIA_API_URL } from './constants';

export const getAllPostsFromServer = async () => {
  //   get all posts from Server
  try {
    const { data } = await axios.get(POSTS_API_URL);
    return data;
  } catch (error) {
    console.log(error);
  }
};

export const getAuthor = async (id) => {
  try {
    const {
      data: { name },
    } = await axios.get(`${AUTHORS_API_URL}/${id}`);
    return name;
  } catch (error) {
    console.log(error);
  }
};

export const getFeaturedImage = async (id) => {
  try {
    const res = await axios.get(`${MEDIA_API_URL}/${id}`);
    return res.data.guid.rendered;
  } catch (error) {
    console.log(error);
    return '';
  }
};

With that done, head into pages > index.js. Here, we’ll make a request to get all the posts from our WordPress server and store them in a React state.

import Head from 'next/head';
import Post from '../components/Post';
import Footer from '../components/Footer';
import { useState, useEffect } from 'react';
import { getAllPostsFromServer } from '../lib/utils';

export default function Home() {
  const [posts, setPosts] = useState([]);
  useEffect(async () => {
    let mounted = true;
    if (mounted) {
      const postsFromServer = await getAllPostsFromServer();
      setPosts(postsFromServer);
    }
    return () => (mounted = false);
  }, []);
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <Head>
        <title>Aeeiee WordPress React Demo</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className="flex flex-col items-center flex-1 px-20 py-10">
        <h1 className="text-6xl font-bold mt-5 mb-5">Blog</h1>
        <p className="text-xl mb-5">WordPress as a Headless CMS with React</p>
        {posts && (
          <div className="grid grid-cols-2 gap-5">
            {posts.map((post, id) => {
              return (
                <div key={id}>
                  <Post post={post} />
                </div>
              );
            })}
          </div>
        )}
      </main>
      <Footer />
    </div>
  );
}

As you can see, we are rendering a <Post /> component that takes in the post prop returned by our getAllPostsFromServer function.

Let’s create the Post component.

Create a new folder called components. Within that component create a file called Post.js. Our Post component will take in a post object containing the id’s for the media and the author and then make subsequent requests to fetch the name and url respectively.

import Link from 'next/link';
import { useState, useEffect } from 'react';
import { getAuthor, getFeaturedImage } from '../lib/utils';
import parse from 'html-react-parser';
export default function Post({ post }) {
  const [postImgAndAuthor, setPostImgAndAuthor] = useState({ featImgUrl: '', author: '' });
  useEffect(() => {
    let mounted = true;
    if (mounted) {
      const author = getAuthor(post.author);
      const featuredImg = getFeaturedImage(post.featured_media);
      //   resolve the promises in getAuthor and getFeaturedImg async functions using Promise.all
      Promise.all([author, featuredImg]).then((res) => {
        setPostImgAndAuthor({
          author: res[0],
          featImgUrl: res[1],
        });
      });
    }
    return () => {
      mounted = false;
    };
  }, []);
  return (
    <div>
      <img className="excerpt-img mb-5" src={postImgAndAuthor ? postImgAndAuthor.featImgUrl : '/aeeiee-logo.png'} />

      <Link href={`/post/${post.id}`}><a className="text-4xl font-bold">{post.title.rendered}</a></Link>
      <h4>{new Date(post.date).toDateString()}</h4>
      <div className="mt-2 relative">
        <div className="mb-2 max-w-lg">{parse(post.excerpt.rendered)}</div>
        <Link href={`/post/${post.id}`}>
          <a className="mt-3 text-blue-800 bottom-0">Continue reading</a>
        </Link>
      </div>
    </div>
  );
}

Notice that we’re building links to each post in our Post component template i.e /post/${post.id}. Later on we’ll create a page that extracts the id’s from the URL in order to pull a specific post from our WordPress server.

You should now be able to see all posts on your landing page.

Displaying Individual Posts

Next, let’s display individual posts. NextJS has inbuilt routing that allows us dynamically render pages and content. You can read more about Dynamic Routes in Nextjs. We’ll use the getStaticProps and getStaticPaths methods to pre-render our pages at build time. Read more here. Pre-rendering pages at build times means that when Nextjs builds and deploys our site, the pages for each post are instantly created at the same time. This ensures that our site loads fast on each route change as it doesn’t need to make additional trips to pull data from our WordPress server when a User tries navigating to a specific post.

Let’s get into it!

In your pages folder, create a folder called post and within that create a file named [id].js . This automatically generates a dynamic route - /post/[postId].js. We’ll then extract the id from the URL and use that to load the post with that id.

import Footer from '../../components/Footer';
import axios from 'axios';
import { getAuthor, getFeaturedImage } from '../../lib/utils';
import { POSTS_API_URL } from '../../lib/constants';

export default function Post({ title, featuredImg, author, content, date }) {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">

    </div>
  );
}

// This function gets called at build time
export async function getStaticPaths() {

  const res = await axios.get(POSTS_API_URL);
  const posts = res.data;
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id.toString() },
  }));
  // We'll pre-render only these paths at build time.
  return { paths, fallback: false };
}
// This also gets called at build time
export async function getStaticProps({ params }) {
  const res = await axios.get(`${POSTS_API_URL}/${params.id}`);
  const post = await res.data;
  const featuredImg = await getFeaturedImage(post.featured_media);
  const author = await getAuthor(post.author);
  return {
    props: { title: post.title.rendered, content: post.content.rendered, featuredImg, author, date: post.date },
  };
}

We need two use two additional methods exposed by Nextjs - getStaticPaths and getStaticProps. The getStaticPaths method will fetch all the postId’s at the point where our site is being built and compiled. Based on the id’s returned from this method, Nextjs will automatically begin the process of pre-rendering pages ensuring that our site remains fast!

Next, we call the getStaticProps method. This method will get all the extra parameters needed for a post using the postId passed down from the getStaticPaths method. getStaticProps will then pass these parameters to our frontend as props.

With all of the server-side work done, we can now write the code for rendering a specific post.

import Footer from '../../components/Footer';
import axios from 'axios';
import parse from 'html-react-parser';
import { getAuthor, getFeaturedImage } from '../../lib/utils';
import { POSTS_API_URL } from '../../lib/constants';
import Head from 'next/head';
import styles from '../../styles/Post.module.css';
export default function Post({ title, featuredImg, author, content, date }) {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <Head>
        <title>{title}</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className="flex flex-col items-center flex-1 mx-5 md:px-20 py-10 max-w-5xl m-auto">
        <h1 className="text-2xl md:text-6xl font-bold mt-5 mb-5 text-center">{title}</h1>
        <div>
          <img src={featuredImg} />
        </div>
        <p className="text-sm mt-5">Written by {author}</p>
        <p className="text-sm font-semibold mb-5">Published on {new Date(date).toDateString()}</p>
        <div className={styles.post}>{parse(content)}</div>
      </main>
      <Footer />
    </div>
  );
}

With that done, we can now test that everything works!

You can see the full code here

 
Share this