Search the site:

Copyright 2010 - 2024 @ DevriX - All rights reserved.

Getting Started with Headless WordPress [Example Included]

Getting Started with Headless WordPress

For the past couple of years, “Headless CMS” has been gaining more and more popularity. The reason is that it really helps improve the performance of your website and makes it easy to build cross-platform applications.

In this article, we’ll guide you through the process of setting up a simple blog using ReactJS and WordPress as a headless CMS via the core REST API. However, before we dive deeper into how to build that, first let us take a look at all the definitions we’ll be using and why “headless” is more often than not, considered the future of web development.

Let’s get started!

What Does “Headless” Even Mean?

By definition, and especially when it comes down to development, the term “headless” means that the tool or project has no GUI ( Graphical User Interface ). You can see it being used in various contexts, including a headless application, server, browser, and as in our case – a headless CMS.

A headless application is one that runs only “in shell” or terminal. Pretty much the same goes for a headless server instance that you can access only via Secure Shell (SSH) connection. This way, you have no actual visual representation of what you’re doing apart from the text output in the console. Headless browsers, for example, are used in automated testing, simulating applications or websites’ behavior without any visual environment, but just as if they had one.

Last, but definitely not least, we have the headless CMS. To put it simply, the logic of the architecture implies removing the visual presentation, thus having no theme, layout, or any other front-end boundaries.

Traditional vs Headless CMS:

Traditional:

The traditional CMS provides you with everything that a headless one would, but on top of that, it comes with some predefined templating, content structure, and alignment when it’s delivered to the end-user.

For example, imagine that you are running a recipes blog on a standard WordPress installation, with a free or paid theme you got from some marketplace. It has an awesome front-end design that you really like and everything has been running smoothly for years now. But have you ever wondered what happens in the background?

Every time you want to add a new article to your blog, you go to the WordPress dashboard ( or administration area ), create a new post and write down the ingredients and cooking steps. Then you probably add a few tags and assign it to a category that fits the topic, and hit the publish button. And voalá – your blog is updated, the new post shows on your homepage and category listing. For this purpose, the traditional CMS is a great choice as it requires basic technical skills and competences.

Quick disclaimer: As of WordPress 5.0, when the Gutenberg editor was introduced, we have quite more flexibility when it comes down to content structure. But still, there are some limitations based on the installed theme.

From a technical view, the flow of adding new content to your website is a bit different than the steps you actually see. The WordPress dashboard can be considered a “back-end UI” that you use to input your posts and pages, which are then stored in the database.With the help of the WordPress core, the plugins, and your theme, the content is delivered to the end-user, with some pretty and responsive styling.

How Traditional CMS Works
Source

Awesome, right?

However, if you’re a large enterprise with a high volume of visitors to your website, and as your audience grows – so do your needs as well. For instance, you might want to have a dedicated website for desserts only, or for veggie dishes. Or maybe you want a mobile application that allows easy access to your followers or allows them to receive notifications as soon as you add a new post.. But you also find it overwhelming to keep track of your posts across multiple instances and you want centralized content management, while it’s still distributed through multiple channels.

Here’s when the headless CMS comes into play!

The Headless CMS Architecture:

The headless architecture allows you to have a single WordPress instance ( administration area only) to manage your content and fetch it from multiple other instances, platforms, or applications. This is possible thanks to the REST API that the WordPress core provides out of the box. You still can publish your posts in the same way as you traditionally do. But from a technical perspective, the process stops at storing the data in the database and the rest is replaced with API calls that should not necessarily be from the same origin.

The Headless CMS Architecture
Source

The Pros and Cons of a Headless CMS:

If you’re still not sure whether a headless CMS is what you need, here’s a short comparison of the two approaches, outlining the benefits and challenges that come with each. Consider sticking with the traditional WordPress setup when you:

  • rely on multiple plugins’ functionalities.
  • your content heavily depends on the WYSIWYG editor.
  • your site requires daily maintenance.
  • you are not a developer or lack deeper knowledge of JavaScript.

Respectively, consider migration to a headless CMS solution if you:

  • want to distribute your content through different channels in an easy manner.
  • need flexibility in the designs depending on the request origin without compromising the platform performance.
  • want to get rid of the heavy codebase that comes with third-party themes.
  • are looking to owning a “future-proof” website.

How to Set Up a Headless CMS [Example]:

Now that we have cleared out what a headless CMS is, why and when you should use it, and how the backend operates, let’s dive into the implementation itself.

Step 1: Environment Setup

As we are going to use ReactJS to create views for our blog example, we need to first get the environment all set up.

If you don’t have NPM and Node.js, go ahead and get those going first. You can check their official documentation to see how to do that depending on your operating system.

And that’s all you need for now.

Step 2: Create React App

As of version 5.2 of NPM, we have a new tool – NPX. That is a tool that allows you to use packages, the same way that you would with NPM but without having to install them explicitly. Instead, you can execute any package that you want straight from the NPM registry.

So now, to create our future React app, we’ll use Facebook’s Create React App ( CRA ). The only thing we have to do is run the following command npx create-react-app <project-name-here> and wait for the installation to finish.

It’ll go through installing all the packages that it needs as a starter and give a notification when it’s ready, also showing you a set of commands that you can use out of the box.

Create React App

Once the waiting is over ( as it might take a while ), you can switch to the newly created directory and run npm start to preview the base version that create-react-app generates.

Create React App Directory

Beautiful, isn’t it?

Step 3: Installing the Needed Packages

More often than not, you won’t know all the packages that every single bit of your app will need, and you may have to add more packages as you continue working and extending the functionality . However, since we’ve already built that, to ease the process, you can install the following set of packages:

  • react-router-dom – easing the blog navigation
  • react-dotenv – allowing .env files to be used to define global constants
  • axios – used to fetch data from the headless WordPress in the background
  • html-react-parser – a really good package helping with parsing HTML strings
  • react-bootstrap bootstrap – provider of Bootstrap components to use in React apps
  • [email protected] – allowing usage of Sass stylesheet instead of plain CSS, but pay attention to the version as the latest (5.0.0) does not really work well with CRA

And a bunch of FontAwesome entries to add beautiful icons. Although we’ll have only one in this example, all three packages would be needed.

@fortawesome/fontawesome-svg-core
@fortawesome/free-solid-svg-icons
@fortawesome/react-fontawesome

Step 4: Components

When you create a new project via CRA, it will automatically create a new folder for it with the default files and sub-directories needed. Among those you’ll find the src folder which is where you should put all the custom JS and CSS that you write. If you place it out of the src folder – it won’t be processed by Webpack, thus no changes will get reflected on the actual app.

Now that we know where to place everything, it’s time to create our custom components. Considering we are building a blog, what components would we need? A header, a footer, an archive view that will list all of our posts and single view for each of the posts, right?

Awesome, let’s do that!

First, we’ll add a components folder, where we’ll store all custom entries and then go one level deeper with a sub-directory for each component to give our project a cleaner look and make the navigation through the codebase easier. Adding also a CSS module for each component should have you end up with a source structure similar to the one on the right. On the right we’ve added the default one that’s created by CRA.

React App Components

Step 5: Actual implementation

Now that we’ve defined all the components we’ll need, let’s get down to coding, shall we?

The root of our application now is App.js, let’s check what we’ve done to it and we’ll break down every change to make sure it all makes sense.

App.js

import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import Header from "./components/Header/Header";
import Footer from "./components/Footer/Footer";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
import PostsList from "./components/PostsList/PostsList";
import Single from "./components/Single/Single";

function App() {
 return (
   <div className="App">
     <Router>
       <Header />
       <Switch>
         <Route exact path="/" component={PostsList} />
         <Route path="/category/:category" component={PostsList} />
         <Route path="/blog/:post" component={Single} />
       </Switch>
       <Footer />
     </Router>
   </div>
 );
}

export default App;

Imports:

import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import Header from "./components/Header/Header";
import Footer from "./components/Footer/Footer";
import { Route, BrowserRouter as Router, Switch } from "react-router-dom";
import PostsList from "./components/PostsList/PostsList";
import Single from "./components/Single/Single";

On top of each component definition file, we have imports that include all dependencies, other components or assets that we’ll need to get the current one to work properly. Here we have the dedicated CSS file, Bootstrap CSS, a set of components from the react-router-dom package, and pretty much all of our custom components – header, footer, posts listing and the single component.

The App component:

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

export default App;

The App component comes from the default setup but what’s more important now is the changes to it’s children that we’ve made.

Router:

     <Router>
       <Header />
       <Switch>
         <Route exact path="/" component={PostsList} />
         <Route path="/category/:category" component={PostsList} />
         <Route path="/blog/:post" component={Single} />
       </Switch>
       <Footer />
     </Router>

Being the closest component to the root level of our React application, the App component will hold the router responsible for switching the views on the user’s end. Since we will always have  a header and  footer on our pages, we can add them as direct children of the <Router> component, but not wrapped in the Switch or other component.

You can usually put static components outside of the router, but there are some limitations like the rule that you should not use <Link> outside a <Router>. Because of that and the fact that we will have links in the header to redirect to different pages, we’ll get it wrapped in the Router, along with everything else, to be on the safe side.

Going further down the structure, we have a <Switch> component that works in quite a similar way to a switch statement which appears in every programming language. It compares a value to a predefined set of cases and a match defines what the next action would be.

If you go back to the imports section, you can see that the <Router> component actually represents a <BrowserRouter>. The BrowserRouter is a type of Router that uses the HTML5 history API and regular URL paths, unlike the HashRouter for example.

As such, in the Switch we’ll have <Route> components, defining the different URL cases that we can have. Each of the <Route> components has “path” and “component” attributes (which are then parsed as props since it’s JSX that we’re writing in,  not plain HTML) defining both the URL that the user should be on and the output respectively. You may notice that to the first route defined, there’s an additional attribute added “exact”. As obvious as it can be, this means that there should be an exact match to the path set, as otherwise it would match every URL path that has a leading slash.

Now that we’ve configured the “views controller” we can proceed with the rest of the components. Starting from top to bottom, first comes the Header. We’ll have a title on the left that’d serve as a button to go back to the home page and a categories listing on the right making it easier for the users to browse articles that match their interests.

Header.js

import React, { useEffect, useState } from "react";
import axios from "axios";
import { NavDropdown } from "react-bootstrap";
import { Link } from "react-router-dom";

import styles from "./Header.module.scss";

const Header = () => {
 const [categories, setCategories] = useState([]);

 useEffect(() => {
   // Fetch categories data.
   axios.get(`${process.env.REACT_APP_WP_REST_BASE}categories`).then((res) => {
     if (!!res["data"]) {
       let list = [];
       res["data"].forEach((element, index) => {
         list.push(
           <NavDropdown.Item
             href={`/category/${element.slug}-${element.id}`}
             key={index}
           >
             {element.name}
           </NavDropdown.Item>
         );
       });
       setCategories(list);
     }
   });
 }, []);

 const markup = (
   <header className={styles["header"]}>
     <div className={styles["home"]}>
       <Link to="/">My awesome blog!</Link>
     </div>
     <div className={styles["navigation"]}>
       <NavDropdown title="Categories" id="basic-nav-dropdown">
         {categories}
       </NavDropdown>
     </div>
   </header>
 );

 return markup;
};

export default Header;

It might look a bit complex or messed up, but it’s actually quite straightforward once you get the gist of it. Let’s break this component down as well.

Styles:

If you remember earlier in the article I mentioned that we’ll be using CSS modules to keep the styling of each component clean and separated from everything else. Now it’s time to include that “module” to be able to use it.

import styles from "./Header.module.scss";

Important note here: Although we’re using import to include the Sass stylesheet, unlike the rest of the entries, it won’t work as a component but a variable instead.

React Hooks:

To get everything in this component working properly, we’ll utilize React Hooks, the “useState” and “useEffect” hooks to be more precise. Let’s go through those real quick.

useState:

To put it simply, that’s the way to use state variables in functional components. In case you’re not familiar with what a state variable is, which would be totally fine, it’s a variable bound to a setter function, which automatically forces the component to re-render when the state value changes. You can also set a default value as a parameter of the useState method call, in our case – it’ll be an empty array to hint the expected format.

const [categories, setCategories] = useState([]);

useEffect:

Unlike the useState hook, this is a built-in function, accepting a callback function and dependencies as arguments. This hook is triggered when the component it’s defined in is rendered. In order to avoid infinite loops or similarly looking cases that happen more often than not, because of all the components re-rendering that happens, we’ll pass an empty array as the second argument which would force it to only run on mount and unmount.

Given the behavior described, we’ll use this hook to fetch the categories that we have in our WordPress back-end and build the list items for the dropdown menu. I like to use axios as it’s easier to work with when the response is in JSON format so this will be the solution in the example, but the same results can surely be achieved by using the fetch() method that’s built-in in most of the modern browsers.

axios.get(`${process.env.REACT_APP_WP_REST_BASE}categories`).then((res) => {
   if (!!res["data"]) {
     let list = [];
     res["data"].forEach((element, index) => {
       list.push(
         <NavDropdown.Item
           href={`/category/${element.slug}-${element.id}`}
           key={index}
         >
           {element.name}
         </NavDropdown.Item>
       );
     });
     setCategories(list);
   }
}); 

Since axios is Promise based, we can simply wait for the JSON response to be parsed and use the result data in the callback function. As the response contains more than simply the return data from the server, we have to verify that the ‘data’ index has a value, then we can loop through the items and build the entries with proper links and text that’ll output in the <NavDropdown> component. Once everything is processed, we can use the setter method from the useState hook and assign the list as the value of the state variable.

Now it’s time to collect it altogether, building the return value of the <Header> component.

const markup = (
   <header className={styles["header"]}>
     <div className={styles["home"]}>
       <Link to="/">My awesome blog!</Link>
     </div>
     <div className={styles["navigation"]}>
       <NavDropdown title="Categories" id="basic-nav-dropdown">
         {categories}
       </NavDropdown>
     </div>
   </header>
 );

 return markup;

Wrapping it all in a header tag, we use the “home button” first and then the categories navigation dropdown. You can see how the styles variable mentioned is passed to the className attribute with index linking to the exact class definition from the CSS module.

Since we’re building a blog, on the homepage we’ll have a list of the latest articles published, which leads us to the PostsList component.

PostsList.js

import React, { useEffect, useState } from "react";
import parse from "html-react-parser";
import { Link } from "react-router-dom";
import axios from "axios";

import styles from "./PostsList.module.scss";

const PostsList = (props) => {
 const { match } = props;

 const [posts, setPosts] = useState([]);

 useEffect(() => {
   let url = `${process.env.REACT_APP_WP_REST_BASE}posts`;
   if (match.params.hasOwnProperty("category")) {
     // Some hardcore parsing.
     const cat_id = match.params.category.split("-").slice(-1).pop();
     url += `?categories=${cat_id}`;
   }
   // Fetch categories data.
   axios.get(url).then((res) => {
     setPosts(res["data"]);
   });
 }, [match]);

 const generateListItem = (post, key) => {
   // Do some hax if there's no featured image to make it not look broken.
   const image =
     null !== post.images.medium
       ? post.images.medium
       : `https://picsum.photos/500?random=${key}`;

   const listItem = (
     <div className={styles["list-item"]} key={key}>
       <div className={styles["post-image"]}>
         <img src={image} alt={image} />
       </div>
       <div className={styles["post-content"]}>
         <h3>{post.title.rendered}</h3>
         {parse(post.excerpt.rendered)}
         <Link
           to={`/blog/${post.slug}-${post.id}`}
           className={styles["button-read-more"]}
         >
           Read more
         </Link>
       </div>
     </div>
   );

   return listItem;
 };

 const markup = (
   <div className={styles["list-posts"]}>
     {posts.map((element, index) => {
       return generateListItem(element, index);
     })}
   </div>
 );

 return markup;
};

export default PostsList;

Not too much different than the Header component when broken down into pieces, but I want to outline a few key points here. Since the current component is always passed as an attribute to a <Route>, it automatically adds a “match” property that can be used to identify what page we are on. Passing it as a dependency would force re-rendering when navigating through pages that have the same main component and an overlapping URL pattern.

To avoid complex quries, I’ve added the category and post IDs to the end of the URLs which introduced some hardcore parsing but once it’s done – we have a proper WordPress REST API endpoint URL to fetch posts from.

const { match } = props;
.
.
.
let url = `${process.env.REACT_APP_WP_REST_BASE}posts`;
if (match.params.hasOwnProperty("category")) {
  // Some hardcore parsing.
  const cat_id = match.params.category.split("-").slice(-1).pop();
  url += `?categories=${cat_id}`;
}

I’ve also added a separate function that would generate the list items to avoid complex markup generated when parsing the data fetched from the backend and a small hack for featured images, because not every post in my test environment had one (props to Picsum for the help).

As we’re going top-to-bottom, we have one more main component to cover before we wrap-up with a footer.

Single.js

import React, { useState, useEffect } from "react";
import parse from "html-react-parser";
import axios from "axios";

import styles from "./Single.module.scss";

const Single = (props) => {
 const { match } = props;

 const [postData, setPostData] = useState([]);

 useEffect(() => {
   const post_id = match.params.post.split("-").slice(-1).pop();
   // Fetch categories data.
   axios
     .get(`${process.env.REACT_APP_WP_REST_BASE}posts/${post_id}`)
     .then((res) => {
       setPostData(res["data"]);
     });
 }, []);

 const markup = (
   <div className={styles["post-wrapper"]}>
     {Object.keys(postData).length > 0 && (
       <div className={`${styles["post-single"]}`}>
         <h1 className={styles["post-heading"]}>{postData.title.rendered}</h1>
         {!!postData.images.large && (
           <div className={styles["post-image"]}>
             <img src={postData.images.large} alt={postData.images.large} />
           </div>
         )}
         <div className={styles["post-content"]}>
           {parse(postData.content.rendered)}
         </div>
       </div>
     )}
   </div>
 );

 return markup;
};

export default Single;

What’s different and what you should pay attention to here is the following line:

{parse(postData.content.rendered)}

That’s important because the editor in the WordPress backend, by default, adds some markups, like paragraph tags or images if you have such in the content, which will be output as a string unless you use the html-react-parser.

Last but not least, we have the footer of the blog.

Footer.js

import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHeart } from "@fortawesome/free-solid-svg-icons";

import styles from "./Footer.module.scss";

const Footer = () => {
 const markup = (
   <footer className={styles["site-footer"]}>
     <span>
       Developed with <FontAwesomeIcon icon={faHeart} /> by fear-sfx
     </span>
   </footer>
 );
 return markup;
};

export default Footer;

Nothing special here, it’s a small but important final touch to make the page look complete and utilize the FontAwesome package that we added when we started.

Here’s what all of the above should look like at the end (with some styling applied of course).

[IMAGE]

My Awesome Blog
Source

I won’t go over the styling applied, you can check out all the files, both the JS and stylesheets used, in git over at this link. Feel free to clone it, play around with it, add some tweaks of your own, and share the upgrades you’ve made!

Hope this helps you out!