I believe that a traditional WordPress theme should be able to work as effectively as a static site or a headless web app. The overwhelming majority of WordPress websites are built with a good ol’ fashioned WordPress theme. Most of them even have good caching layers, and dependency optimizations that make these sites run reasonably fast. But as developers, we have accomplished ways to create better results for our websites. Using a headless WordPress has allowed many sites to have faster load speeds, better user interactions, and seamless transitions between pages.
The problem? Maintenance. Let me show you another possibility!
Let’s start by defining what I mean by “Traditional” WordPress, “Headless” WordPress, and then “Nearly Headless” WordPress.
Traditional WordPress websites
Traditionally, a WordPress website is built using PHP to render the HTML markup that is rendered on the page. Each time a link is clicked, the browser sends another request to the server, and PHP renders the HTML markup for the site that was clicked.
This is the method that most sites use. It’s the easiest to maintain, has the least complexity in the tech, and with the right server-side caching tools it can perform fairly well. The issue is, since it is a traditional website, it feels like a traditional website. Transitions, effects, and other stylish, modern features tend to be more difficult to build and maintain in this type of site.
Pros:
The site is easy to maintain.
The tech is relatively simple.
There is great compatibility with WordPress plugins.
Cons:
Your site may feel a little dated as society expects app-like experiences in the browser.
JavaScript tends to be a little harder to write and maintain since the site isn’t using a JavaScript framework to control the site’s behavior.
Traditional websites tend to run slower than headless and nearly headless options.
Headless WordPress websites
A headless WordPress website uses modern JavaScript and some kind of server-side RESTful service, such as the WordPress REST API or GraphQL. Instead of building, and rendering the HTML in PHP, the server sends minimal HTML and a big ol’ JavaScript file that can handle rendering any page on the site. This method loads pages much faster, and opens up the opportunity to create really cool transitions between pages, and other interesting things.
No matter how you spin it, most headless WordPress websites require a developer on-hand to make any significant change to the website. Want to install a forms plugin? Sorry, you probably need a developer to set that up. Want to install a new SEO plugin? Nope, going to need a developer to change the app. Wanna use that fancy block? Too bad — you’re going to need a developer first.
Pros:
The website itself will feel modern, and fast.
It’s easy to integrate with other RESTful services outside of WordPress.
The entire site is built in JavaScript, which makes it easier to build complex websites.
Cons:
You must re-invent a lot of things that WordPress plugins do out of the box for you.
This set up is difficult to maintain.
Compared to other options, hosting is complex and can get expensive.
See “WordPress and Jamstack” for a deeper comparison of the differences between WordPress and static hosting.
I love the result that headless WordPress can create. I don’t like the maintenance. What I want is a web app that allows me to have fast load speeds, transitions between pages, and an overall app-like feel to my site. But I also want to be able to freely use the plugin ecosystem that made WordPress so popular in the first place. What I want is something headless-ish. Nearly headless.
I couldn’t find anything that fit this description, so I built one. Since then, I have built a handful of sites that use this approach, and have built the JavaScript libraries necessary to make it easier for others to create their own nearly headless WordPress theme.
Introducing Nearly Headless WordPress
Nearly headless is a web development approach to WordPress that gives you many of the app-like benefits that come with a headless approach, as well as the ease of development that comes with using a traditional WordPress theme. It accomplishes this with a small JavaScript app that will handle the routing and render your site much like a headless app, but has a fallback to load the exact same page with a normal WordPress request instead. You can choose which pages load using the fallback method, and can inject logic into either the JavaScript or the PHP to determine if the page should be loaded like this.
You can see this in action on the demo site I built to show off just what this approach can do.
For example, one of the sites implementing this method uses a learning management system called LifterLMS to sell WordPress courses online. This plugin has built-in e-commerce capabilities, and sets up the interface needed to host and place course content behind a paywall. This site uses a lot of LifterLMS’s built-in functionality to work — and a big part of that is the checkout cart. Instead of re-building this entire page to work inside my app, I simply set it to load using the fallback method. Because of this, this page works like any old WordPress theme, and works exactly as intended as a result — all without me re-building anything.
Pros:
This is easy to maintain, when set-up.
The hosting is as easy as a typical WordPress theme.
The website feels just as modern and fast as a headless website.
Cons:
You always have to think about two different methods to render your website.
There are limited choices for JavaScript libraries that are effective with this method.
This app is tied very closely to WordPress, so using third party REST APIs is more-difficult than headless.
How it works
For something to be nearly headless, it needs to be able to do several things, including:
load a page using a WordPress request,
load a page using JavaScript,
allow pages to be identical, regardless of how they’re rendered,
provide a way to know when to load a page using JavaScript, or PHP, and
Ensure 100% parity on all routed pages, regardless of if it’s rendered with JavaScript or PHP.
This allows the site to make use of progressive enhancement. Since the page can be viewed with, or without JavaScript, you can use whichever version makes the most sense based on the request that was made. Have a trusted bot crawling your site? Send them the non-JavaScript version to ensure compatibility. Have a checkout page that isn’t working as-expected? Force it to load without the app for now, and maybe fix it later.
To accomplish each of these items, I released an open-source library called Nicholas, which includes a pre-made boilerplate.
Keeping it DRY
The biggest concern I wanted to overcome when building a nearly-headless app is keeping parity between how the page renders in PHP and JavaScript. I did not want to have to build and maintain my markup in two different places — I wanted a single source for as much of the markup as possible. This instantly limited which JavaScript libraries I could realistically use (sorry React!). With some research, and a lot of experimentation, I ended up using AlpineJS. This library kept my code reasonably DRY. There’s parts that absolutely have to be re-written for each one (loops, for example), but most of the significant chunks of markup can be re-used.
A single post template rendered with PHP might look like something like this:
Both of them use the same PHP template, so all of the code inside the actual loop is DRY:
$ title = $ template->get_param( 'title', '' ); // Get the title that was passed into this template, fallback to empty string.$ content = $ template->get_param( 'content', '' ); // Get the content passed into this template, fallback to empty string. ?> <article x-data="theme.Post(index)"> <!-- This will use the alpine directive to render the title, or if it's in compatibility mode PHP will fill in the title directly --> <h1 x-html="title"><?= $ title ?></h1> <!-- This will use the Alpine directive to render the post content, or if it's in compatibility mode, PHP will fill in the content directly --> <div class="content" x-html="content"><?= $ content ?></div> </article>
Detect when a page should run in compatibility mode
“Compatibility mode” allows you to force any request to load without the JavaScript that runs the headless version of the site. When a page is set to load using compatibility mode, the page will be loaded using nothing but PHP, and the app script never gets enqueued. This allows “problem pages” that don’t work as-expected with the app to run without needing to re-write anything.
There are several different ways you can force a page to run in compatibility mode — some require code, and some don’t. Nicholas adds a toggle to any post type that makes it possible to force a post to load in compatibility mode.
Along with this, you can manually add any URL to force it to load in compatibility mode inside the Nicholas settings.
These are a great start, but I’ve found that I can usually detect when a page needs to load in compatibility mode automatically based on what blocks are stored in a post. For example, let’s say you have Ninja Forms installed on your site, and you want to use the validation JavaScript they provide instead of re-making your own. In this case, you would have to force compatibility mode on any page that has a Ninja Form on it. You could manually go through and add each URL as you need them, or you can use a query to get all of the content that has a Ninja Forms block on the page. Something like this:
That automatically adds any page with a Ninja Forms block to the list of URLs that will load using compatibility mode. This is just using WP_Query arguments, so you could pass anything you want here to determine what content should be added to the list.
Extending the app
Under the hood, Nicholas uses a lightweight router that can be extended using a middleware pattern much like how an Express app handles middleware. When a clicked page is routed, the system runs through each middleware item, and eventually routes the page. By default, the router does nothing; however, it comes with several pre-made middleware pieces that allows you to assemble the router however you see-fit.
A basic example would look something like this:
// Import WordPress-specific middleware import { updateAdminBar, validateAdminPage, validateCompatibilityMode } from 'nicholas-wp/middlewares' // Import generic middleware import { addRouteActions, handleClickMiddleware, setupRouter, validateMiddleware } from "nicholas-router"; // Do these actions, in this order, when a page is routed. addRouteActions( // First, validate the URL validateMiddleware, // Validate this page is not an admin page validateAdminPage, // Validate this page doesn't require compatibility mode validateCompatibilityMode, // Then, we Update the Alpine store updateStore, // Maybe fetch comments, if enabled fetchComments, // Update the history updateHistory, // Maybe update the admin bar updateAdminBar ) // Set up the router. This also uses a middleware pattern. setupRouter( // Setup event listener for clicks handleClickMiddleware )
From here, you could extend what happens when a page is routed. Maybe you want to scan the page for code to highlight, or perhaps you want to change the content of the <head> tag to match the newly routed page. Maybe even introduce a caching layer. Regardless of what you need to-do, adding the actions needed is as simple as using addRouteAction or setupRouter.
Next steps
This was a brief overview of some of the key components I used to implement the nearly headless approach. If you’re interested in going deeper, I suggest that you take my course at WP Dev Academy. This course is a step-by-step guide on how to build a nearly headless WordPress website with modern tooling. I also suggest that you check out my nearly headless boilerplate that can help you get started on your own project.
This post was in progress before Automattic acquired Frontity and its entire team. According to Frontity’s founders, the framework will be transitioned into a community-led project and leave the project in “a stable, bug-free position” with documentation and features. Like other open-source community projects, Frontity will remain free as it has been, with opportunities to contribute to the project and make it an even better framework for decoupled WordPress. More detail is found in this FAQ page.
In my previous article, we created a headless WordPress site with Frontity and briefly looked at its file structure. In this companion article, we will go into a deep dive of the @frontity/mars-theme package, or Mars Theme, with a step-by-step walkthrough on how to customize it to make our own. Not only is the Mars Theme a great starter, it’s Frontity’s default theme — sort of like WordPress Twenty Twenty-One or the like. That makes it a perfect starting point for us to get hands-on experience with Frontity and its features.
Specifically, we will look at the fundamental parts of Frontity’s Mars Theme, including what they call “building blocks” as well as the different components that come with the package. We’ll cover what those components do, how they work, and finally, how styling works with examples.
Let’s revisit the file structure of the Frontity project we made in the last article as that shows us exactly where to find Frontity’s building blocks, the frontity.settings.js, and package.json and packages/mars-theme folder. We covered these is great detail before but, in particular, the package.json file gives us a lot of information about the project, like the name, description, author, dependencies, etc. Here’s what that file includes:
frontity: this is the main package that includes all the methods used in Frontity app development. It’s also where the CLI lives.
@frontity/core: This is the most important package because it takes care of all the bundling, rendering, merging, transpiling, serving, etc. We don’t need to access to it in order to develop a Frontity app. The full list is captured in the Frontity docs.
@frontity/wp-source:This package connects to the WordPress REST API of our site and fetches all the data needed in the Mars Theme.
@frontity/tiny-router:This package handles window.history and helps us with routing.
@frontity/htmal2react:This package converts HTML to React, working with processors that match HTML portions while replacing them with React components.
Frontity core, or @frontity/package (also referred as Frontity’s building block), is composed of useful React component libraries in its @frontity/components package, which exports helpful things like Link, Auto Prefetch, Image, Props, Iframe, Switch, and other functions, objects, etc., that can be directly imported into Frontity project components. A more detailed description of these components—including syntax info use cases—is in this package reference API.
When starting frontity, all the packages defined in frontity.settings.js are imported by @frontity/file-settings and the settings and exports from each package are merged by @frontity/core into a single store where you can access the state and actions of the different packages during development using @frontity/connect, the frontity state manager.
Next up, we’re familiarizing ourselves with how these building blocks, utilities and exports are used in the Mars Theme package to create a functioning Frontity project with a headless WordPress endpoint.
Section 1: Digging into the Mars Theme
Before discussing styling and customizing let’s briefly familiarize ourselves with the Mars Theme (@frontity/mars-theme) file structure and how it is put together.
The Mars Theme has three important component files: /src/index.js file, src/list/index.js and src/components/index.js. Frontity’s documentation is a great resource for understanding the Mars Theme, with especially great detail on how different Mars Theme components are defined and connected together in a Frontity site. Let’s start familiarizing ourselves with the theme’s three most important components: Root, Theme and List.
Theme Root component (/src/index.js)
The src/index.js file, also known as the theme’s Root, is one of the most important Mars Theme components. The Root serves as an entry point that targets <div id="root"> in the site markup to inject the roots of all the installed packages required to run a Frontity project. A Frontity theme exports a root and other required packages in the DOM as shown in the following use case example from the Frontity documentation:
This is everything the package pulls in when initializing the Root component:
// mars-theme/src/components/index.js import Theme from "./components"; // import processor libraries import image from "@frontity/html2react/processors/image"; import iframe from "@frontity/html2react/processors/iframe"; import link from "@frontity/html2react/processors/link"; const marsTheme = { // The name of the extension name: "@frontity/mars-theme", // The React components that will be rendered roots: { /** In Frontity, any package can add React components to the site. * We use roots for that, scoped to the `theme` namespace. */ theme: Theme, }, state: { /** State is where the packages store their default settings and other * relevant state. It is scoped to the `theme` namespace. */ theme: { autoPrefetch: "in-view", menu: [], isMobileMenuOpen: false, featured: { showOnList: false, showOnPost: false, }, }, }, /** Actions are functions that modify the state or deal with other parts of * Frontity-like libraries. */ actions: { theme: { toggleMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen; }, closeMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = false; }, }, }, /** The libraries that the extension needs to create in order to work */ libraries: { html2react: { /** Add a processor to `html2react` so it processes the `<img>` tags * and internal link inside the content HTML. * You can add your own processors too. */ processors: [image, iframe, link], }, }, }; export default marsTheme;
The Mars Theme root component exports packages that includes any of the roots, fills, state, actions and libraries elements. More detailed information on Root can be found in this Frontity doc.
Theme component (/src/components/index.js)
The Frontity Theme component is its main root level component that is exported by the Theme namespace (lines 12-16, highlighted in the previous example. The Theme component is wrapped with the @frontity/connect function (line 51, highlighted below) which provides access to its state, actions and libraries props from the Root component instance and allows Theme component to read the state, manipulate through actions, or use code from other features packages in the libraries.
// mars-theme/src/components/index.js import React from "react" // Modules from @emotion/core, @emotion/styled, css, @frontity/connect, react-helmet import { Global, css, connect, styled, Head } from "frontity"; import Switch from "@frontity/components/switch"; import Header from "./header"; import List from "./list"; import Post from "./post"; import Loading from "./loading"; import Title from "./title"; import PageError from "./page-error"; /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> {/* Add some metatags to the <head> of the HTML with react-helmet */} <Title /> <Head> <meta name="description" content={state.frontity.description} /> <html lang="en" /> </Head> {/* Add some global styles for the whole site, like body or a's. Not classes here because we use CSS-in-JS. Only global HTML tags. */} <Global styles={globalStyles} /> {/* Render Header component. Add the header of the site. */} <HeadContainer> <Header /> </HeadContainer> {/* Add the main section. It renders a different component depending on the type of URL we are in. */} <Main> <Switch> <Loading when={data.isFetching} /> <List when={data.isArchive} /> <Post when={data.isPostType} /> <PageError when={data.isError} /> </Switch> </Main> </> ); }; export default connect(Theme); {/* define Global styles and styled components used Theme component here */} const globalStyles = css` body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; } a, a:visited { color: inherit; text-decoration: none; } `; const HeadContainer = styled.div` // ... `; const Main = styled.div` // ... `;
This example is pulled directly from the Mars Theme’s /src/components/index.js component, which we imported with connect from frontity (line 4, above). We are using state.source.get() to retrieve data to be rendered from the current path (lines 39-46, highlighted above); for example, List, Post and other components.
Section 2: Working with the List component
What we just looked at are the theme-level components in Frontity’s Mars Theme. You may have noticed that those components import additional components. Let’s look at a specific one of those, the List component.
The List component is exported by src/components/list/index.js which uses @loadable/components to split the List component code in such a way that the component only loads when a user clicks a List view; otherwise it won’t render at all, like when a Post view is clicked instead.
// src/components/list/index.js import { loadable } from "frontity"; // Codesplit the list component so it's not included if the users // load a post directly. export default loadable(() => import("./list"));
In this example, Frontity utilizes loadble functions (integrated from Loadable components) for code splitting which loads a component asynchronously and separates code into different bundles that are dynamically loaded at run time. Frontity’s core package API reference goes into much more detail.
Displaying lists of posts
To display a list of posts in an archive page, we first have to look Frontity src/components/list/list.js component. As the name suggests, the List component renders lists of posts using state.source.get(link) and its items field (lines 22-25, highlighted below).
// src/components/list/list.js import { connect, styled, decode } from "frontity"; import Item from "./list-item"; import Pagination from "./pagination"; const List = ({ state }) => { // Get the data of the current list. const data = state.source.get(state.router.link); return ( <Container> {/* If the list is a taxonomy, we render a title. */} {data.isTaxonomy && ( <Header> {data.taxonomy}: {state.source[data.taxonomy][data.id].name} </Header> )} {/* If the list is an author, we render a title. */} {data.isAuthor && ( <Header>Author: {state.source.author[data.id].name}</Header> )} {/* Iterate over the items of the list. */} {data.items.map(({ type, id }) => { const item = state.source[type][id]; // Render one Item component for each one. return <Item key={item.id} item={item} />; })} <Pagination /> </Container> ); }; export default connect(List);
In the code example above, the connect function is imported by frontity in line 2 and is wrapped around the exported connect(List) component in line 31 (the last line). Two other components, list-item.js and pagination.js are also imported. Let’s look at those next!
Here’s what we have for list-item.js:
// src/components/list/list-item.js import { connect, styled } from "frontity"; import Link from "../link"; import FeaturedMedia from "../featured-media"; const Item = ({ state, item }) => { const author = state.source.author[item.author]; const date = new Date(item.date); return ( <article> {/* Rendering clickable post Title */} <Link link={item.link}> <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} /> </Link> <div> {/* If the post has an author, we render a clickable author text. */} {author && ( <StyledLink link={author.link}> <AuthorName> By <b>{author.name}</b> </AuthorName> </StyledLink> )} {/* Rendering post date */} <PublishDate> {" "} on <b>{date.toDateString()}</b> </PublishDate> </div> {/* If the want to show featured media in the * list of featured posts, we render the media. */} {state.theme.featured.showOnList && ( <FeaturedMedia id={item.featured_media} /> )} {/* If the post has an excerpt (short summary text), we render it */} {item.excerpt && ( <Excerpt dangerouslySetInnerHTML={{ __html: item.excerpt.rendered }} /> )} </article> ); }; // Connect the Item to gain access to `state` as a prop export default connect(Item);
The Item component renders the preview of a blog post with clickable post title (lines, 12-14, highlighted above), author name (lines 19-21, highlighted above) and published date (lines: 25-28, highlighted above) along with <FeaturedMedia /> which serves as a post’s optional featured image.
Paginating a list of posts
Let’s look at thePagination component that was rendered earlier in the List component by the src/components/list/pagination/js that follows:
// src/components/list/pagination.js import { useEffect } from "react"; import { connect, styled } from "frontity"; import Link from "../link"; const Pagination = ({ state, actions }) => { // Get the total posts to be displayed based for the current link const { next, previous } = state.source.get(state.router.link); // Pre-fetch the the next page if it hasn't been fetched yet. useEffect(() => { if (next) actions.source.fetch(next); }, []); return ( <div> {/* If there's a next page, render this link */} {next && ( <Link link={next}> <Text>← Older posts</Text> </Link> )} {previous && next && " - "} {/* If there's a previous page, render this link */} {previous && ( <Link link={previous}> <Text>Newer posts →</Text> </Link> )} </div> ); }; /** * Connect Pagination to global context to give it access to * `state`, `actions`, `libraries` via props */ export default connect(Pagination);
The Pagination component is used so that users can paginate between lists of posts — you know, like navigating forward from Page 1 to Page 2, or backward from Page 2 to Page 1. The state, actions, libraries props are provided by the global context that wraps and exports them with connect(Pagination).
Displaying single posts
The Post component displays both single posts and pages. Indeed, structurally both are the same except, in posts, we usually display meta data (author, date, categories etc). Meta data isn’t usually used in pages.
In this Post component, conditional statements are rendered only if the post object contains data (i.e. data.isPost) and a featured image is selected in sate.theme.featured in the theme’s root component:
// src/components/post.js import { useEffect } from "react"; import { connect, styled } from "frontity"; import Link from "./link"; import List from "./list"; import FeaturedMedia from "./featured-media"; const Post = ({ state, actions, libraries }) => { // Get information about the current URL. const data = state.source.get(state.router.link); // Get the data of the post. const post = state.source[data.type][data.id]; // Get the data of the author. const author = state.source.author[post.author]; // Get a human readable date. const date = new Date(post.date); // Get the html2react component. const Html2React = libraries.html2react.Component; useEffect(() => { actions.source.fetch("/"); {/* Preloading the list component which runs only on mount */} List.preload(); }, []); // Load the post, but only if the data is ready. return data.isReady ? ( <Container> <div> <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} /> {/* Only display author and date on posts */} {data.isPost && ( <div> {author && ( <StyledLink link={author.link}> <Author> By <b>{author.name}</b> </Author> </StyledLink> )} <DateWrapper> {" "} on <b>{date.toDateString()}</b> </DateWrapper> </div> )} </div> {/* Look at the settings to see if we should include the featured image */} {state.theme.featured.showOnPost && ( <FeaturedMedia id={post.featured_media} /> )} {/* Render the content using the Html2React component so the HTML is processed by the processors we included in the libraries.html2react.processors array. */} <Content> <Html2React html={post.content.rendered} /> </Content> </Container> ) : null; }; {/* Connect Post to global context to gain access to `state` as a prop. */} export default connect(Post);
Section 3: Links, menus, and featured images
We just saw how important the List component is when it comes to displaying a group of posts. It’s what we might correlate to the markup we generally use when working with the WordPress loop for archive pages, latest posts feeds, and other post lists.
There are a few more components worth looking at before we get into Mars Theme styling.
The Link component (src/components/link.js)
The following MarsLink component comes from src/components/link.js, which is a wrapper on top of the {@link Link} component. It accepts the same props as the {@link Link} component.
// src/components/link.js import { connect, useConnect } from "frontity"; import Link from "@frontity/components/link"; const MarsLink = ({ children, ...props }) => { const { state, actions } = useConnect(); /** A handler that closes the mobile menu when a link is clicked. */ const onClick = () => { if (state.theme.isMobileMenuOpen) { actions.theme.closeMobileMenu(); } }; return ( <Link {...props} onClick={onClick} className={className}> {children} </Link> ); }; // Connect the Item to gain access to `state` as a prop export default connect(MarsLink, { injectProps: false });
As explained in this tutorial, the Link component provides a link attribute that takes a target URL as its value. Quoting from the doc: it outputs an <a> element into the resulting HTML, but without forcing a page reload which is what would occur if you simply added an <a> element instead of using the Link component.
Frontity menu (src/components/nav.js)
Earlier, we defined values for menu items in the frontity.settings.js file. In the Nav component (located in src/components/nav/js) those menu item values are iterated over, match their page url, and display the component inside the Header component.
// src/components/nav.js import { connect, styled } from "frontity"; import Link from "./link"; const Nav = ({ state }) => ( <NavContainer> // Iterate over the menu exported from state.theme and menu items value set in frontity.setting.js {state.theme.menu.map(([name, link]) => { // Check if the link matched the current page url const isCurrentPage = state.router.link === link; return ( <NavItem key={name}> {/* If link URL is the current page, add `aria-current` for a11y */} <Link link={link} aria-current={isCurrentPage ? "page" : undefined}> {name} </Link> </NavItem> ); })} </NavContainer> ); // Connect the Item to gain access to `state` as a prop export default connect(Nav);
Featured Image component (/src/components/featured-media.js)
In Frontity, featured media items values are defined in the Root component ‘s theme.state.featured line that we discussed earlier. Its full code is available in the /src/components/featured-media.js component file.
Now that we’re more familiar with the Mars Theme, as well as its building blocks, components, and functions, we can move into the different approaches that are available for styling the Mars Theme front-end.
As we move along, you may find this Frontity doc a good reference for the various styling approaches we cover.
First off, Frontity provides us with reusable components made with with styled-components, and Emotion, a CSS library for styling components in JavaScript, right out of the box. Emotion is popular with React and JavaScript developers, but not so much in the WordPress community based on what I’ve seen. CSS-Tricks has covered CSS-in-JS in great detail including how it compares with other styling, and this video provides background information about the library. So, knowing that both styled-components and Emotion are available and ready to use is nice context as we get started.
I am new to the CSS-in-JS world, except for some general reading on it here and there. I was exposed to CSS-in-JS styling in a Gatsby project, but Gatsby provides a bunch of other styling options that aren’t readily available in Frontity or the Mars Theme. That said, I feel I was able to get around that lack of experience, and what I learned from my discovery work is how I’m going to frame things.
So, with that, we are going to visit a few styling examples, referencing Frontity’s styling documentation as we go in order to familiarize ourselves with even more information.
Using styled-components
As the name suggests, we need a component in order to style it. So, first, let’s create a styled-component using Emotion’s styled function.
Let’s say we want to style a reusable <Button /> component that’s used throughout our Frontity project. First, we should create a <Button /> component (where its div tag is appended with a dot) and then call the component with a template literal for string styles.
Now this <Button /> component is available to import in other components. Let’s look specifically at the Mars Theme<Header /> component to see how the styled-component is used in practice.
// mars-theme/src/components/header.js import { connect, styled } from "frontity"; import Link from "./link"; import MobileMenu from "./menu"; const Header = ({ state }) => { return ( <> <Container> // This component is defined later <StyledLink link="/"> // This component is defined later <Title>{state.frontity.title}</Title> // This component is defined later </StyledLink> // ... </Container> </> ); }; // Connect the Header component to get access to the `state` in its `props` export default connect(Header); // Defining the Container component that is a div with these styles const Container = styled.div` width: 848px; max-width: 100%; box-sizing: border-box; padding: 24px; color: #fff; display: flex; flex-direction: column; justify-content: space-around; `; // Defining Title component that is h2 with these styles const Title = styled.h2` margin: 0; margin-bottom: 16px; `; // Defining StyledLink component that is a third-party Link component const StyledLink = styled(Link)` text-decoration: none; `;
In the above code example, the <StyledLink /> component (lines 39-41, highlighted above) is used to style another component, <Link />. Similarly. the <Container /> and <Title /> styled-components are used to style the site title and the site’s main container width.
The Emotion docs describe how a styled component can be used as long as it accepts className props. This is a useful styling tool that can be extended using a variable as shown in the following example below from Frontity’s documentation:
// mars-theme/src/components/header.js // ... // We create a variable to use later as an example Const LinkColor = "green"; // ... // Defining StyledLink component that is a third-party Link component const StyledLink = styled(Link)` text-decoration: none; Background-color: $ {linkColor}; `;
The styled component above is used extensively in the Mars Theme. But before we go further, let’s look at using a CSS prop to style components.
Using a CSS prop
The css prop is available as a template literal for inline styling from the Frontity core package. It is similar to styled-components, except css does not return a React component but rather a special object that can be passed to a component through the css prop.
/* Using as CSS prop */ import { css } from "frontity"; const PinkButton = () => ( <div css={css`background: pink`}> My Pink Button </div> );
See that? We can style a component inline using the css prop on a component. Additional use case examples are available in the Emotion docs.
Using the <Global /> component
<Global /> is a React component that allows to us create site-wide general styles, though Frontity does not optimize it for performance. Global styles should be added to the <Theme /> root component.
// packages/mars-theme/src/components/index.js // ... import { Global, css, styled } from "frontity"; import Title from "./title"; import Header from "./header"; // ... // Theme root const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> {/* Add some metatags to the <head> of the HTML. */} <Title /> // ... {/* Add global styles */} <Global styles={globalStyles} /> {/* Add the header of the site. */} <HeadContainer> <Header /> </HeadContainer> // ... </> ); }; export default connect(Theme); const globalStyles = css` body { margin: 0; font-family: -apple-system, "Helvetica Neue", Helvetica, sans-serif; } a, a:visited { color: inherit; text-decoration: none; } `; const HeadContainer = styled.div` // ... `;
The <Global /> component has a style attribute that takes a css function as its value and consists of standard CSS inside back ticks (lines 35-45, highlighted above) as template literals. Frontity recommends using global styles for globally-used HTML tags, like <html>, <body>, <a>, and <img>.
I did a lot of research heading into my Mars Theme project and thought I’d share some of the more useful resources I found for styling Frontity themes:
Official Frontity themes. In addition to the default Mars Theme, Frontity has a ready-to-use package that ports the default WordPress Twenty Twenty theme in its entirety to a Frontity project. You will notice in the next section that my style customizations were inspired by this great learning resource.
Community themes. At this time of this writing, there are a grand total of nine Frontity community members who contributed fully functional theme packages. Those themes can be cloned into your own project and customized according to your needs. Likewise, many of the sites included in the Frontity showcase have GitHub repository links, and just as we can copy or pick up design tips from WordPress themes, we can use these resources to customize our own Frontity theme by referencing these packages.
Creating your own theme from scratch.The Frontity tutorial site has an excellent step-by-step guide to create your own fully working and functional theme package from scratch. Although it’s a little time consuming to go through it all, it is the best approach to fully understand a Frontity site project.
Now that we have covered the more commonly used Frontity styling techniques, let’s apply what we’ve learned to start customizing our Mars Theme project.
Section 5: Customizing the Frontity Mars Theme
I’m going to share one of my working Frontity projects, where I took the Mars Theme as a base and modified it with the resources we’ve covered so far. Because this is my learning playground, I took time to learn from Frontity default themes, community themes and Frontity showcase sites.
So here are examples of how I customized Frontity’s Mars Theme for my headless WordPress site project.
First, I wanted to change the @frontity/mars-theme package name to something different. It’s a good idea to change the package name and make sure all of the dependencies in the package file are up to date. Luis Herrera outlines the required steps for renaming the Mars Theme package in this frontity community forum, which I used as a reference to go from @fontity/mars-theme package to @frontity/labre-theme.
So, open up the package.json file and change the name property on line 2. This is the name of the package that gets used throughout the project.
I renamed my project from mars-theme to labre-theme in my package.json file,.
We should also update the name of the project folder while we’re at it. We can do that on line 25. I changed mine from ./package/mars-theme to ./package/labre-theme. Now, the theme package is properly listed as a dependency and will be imported to the project.
Our frontity-settings.js file needs to reflect the name change. So, let’s open that up and:
rename the package name on line 13 (I changed mine from @frontity/mars-theme to @frontity/labre-theme), and
rename the name on line 3 (I changed mine from mars-demo to labre-demo).
Next up, we want to re-initialize the project with these changes. We should delete the node_modules folder with rm -rf node_modules in a terminal and reinstall the npm package with yarn install. Once the npm package is reinstalled, everything gets properly linked internally and our Frontity project runs just fine without any errors.
Refactoring navigation with dynamic menu fetching
As we discussed earlier, Frontity menu items are either hard-coded in the frontity.setting.js file or in index.js component that’s stored in the Frontity state. However, WordPress can dynamically fetch the Frontity menu. In fact, Frontity just so happens to have a YouTube video onthe subject. Let me break down the key steps here.
The first step is to install the WP-REST-API V2 Menus plugin in WordPress. The plugin is freely available in the WordPress Plugin Directory, which means you can find it and activate it directly from the WordPress admin.
Why do we need this plugin? It extends the new routes to all the registered WordPress menus to the REST API (e.g. /menus/v1/menus/<slug>).
If we check our project site at /wp-json/menu/v1/menus, it should display our selected menu items in the JSON. We can get the menu items with the menu item’s slug property.
Next, let’s use the menuHandler function from the tutorial. Create a new menu-handler.js file at src/components/handler/menu-handler.js and paste in the following code:
// src/components/handler/menu-handler.js const menuHandler = { name: "menus", priority: 10, pattern: "/menu/:slug", func: async ({ link, params, state, libraries }) => { console.log("PARAMS:", params); const { slug } = params; // Fetch the menu data from the endpoint const response = await libraries.source.api.get({ endpoint: `/menus/v1/menus/$ {slug}`, }); // Parse the JSON to get the object const menuData = await response.json(); // Add the menu items to source.data const menu = state.source.data[link]; console.log(link); Object.assign(menu, { items: menuData.items, isMenu: true, }); }, }; export default menuHandler;
This menuHandler function is only executed if the pattern value (i.e. /menu/:slug) matches. Now let’s update our /src/index.js root component so it imports the handler:
// src/index.js import Theme from "./components"; import image from "@frontity/html2react/processors/image"; import iframe from "@frontity/html2react/processors/iframe"; import link from "@frontity/html2react/processors/link"; import menuHandler from "./components/handlers/menu-handler"; const labreTheme = { // ... state: { theme: { autoPrefetch: "in-view", menu: [], {/* Add menuURL property with menu slug as its value */} menuUrl: "primary-menu", isMobileMenuOpen: false, // ... }, }, /** Actions are functions that modify the state or deal with other parts of * Frontity-like libraries */ actions: { theme: { toggleMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen; }, closeMobileMenu: ({ state }) => { state.theme.isMobileMenuOpen = false; }, {/* Added before SSR action */} beforeSSR: async ({ state, actions }) => { await actions.source.fetch(`/menu/$ {state.theme.menuUrl}/`); }, }, }, libraries: { // ... {/* Added menuHandler source */} source: { handlers: [menuHandler], }, }, }; export default labreTheme;
Add an array of handlers under the source property and fetch data before the beforeSSR function. It does not fetch but does match the menu-handler slug, which means menuHandler() is executed. That puts the menu items into state and they become available to manipulate.
Please note that we have added a new menuUrl property here (line 15 above) which can be used as a variable at our endpoint in handlers, as well as the nav.js component. Then, changing the value of menuUrl in the index.js root component, we could display another menu.
Let’s get this data into our theme through state and map with menu-items to display on the site.
// src/components/nav.js import { connect, styled } from "frontity"; import Link from "./link"; /** Navigation Component. It renders the navigation links */ const Nav = ({ state }) => { {/* Define menu-items constants here */} const items = state.source.get(`/menu/$ {state.theme.menuUrl}/`).items; return ( <NavContainer> {items.map((item) => { return ( <NavItem key={item.ID}> <Link link={item.url}>{item.title}</Link> </NavItem> ); })} </NavContainer> ); }; export default connect(Nav); const NavContainer = styled.nav` list-style: none; // ...
If we change our menu slug here and in index.js, then we get a different menu. To view dynamic menu items in mobile view, we should similarly update menu-modal.js components as well.
Additionally, the tutorial describes how to fetch nested menus as well, which you can learn from the tutorial video, starting at about 18:09.
Modifying the file structure
I decided to restructure my Labre (formerly known as Mars) theme folder. Here’s how it looks after the changes:
As you can see, I added separate folders for pages, styles, headers, posts, and images. Please take a note that we have to update file paths in index.js and other related components anytime we change the way files and folders are organized. Otherwise, they’ll be pointing to nothing!
Adding a custom footer component
You may have noticed that the original Mars Theme folder structure includes neither a footer component, nor a separate page component. Let’s make those components to demonstrate how our new folder structure works.
We can start with the page component. The Mars Theme generates both pages and posts with the posts.js component by default — that’s because pages and posts are essentially the same except that posts have meta data (e.g. authors, date, etc.) and they can get away with it. But we can separate them for our own needs by copying the code in posts.js and pasting it into a new pages.js file in our /pages folder.
// src/components/pages/page.js import React, { useEffect } from "react"; import { connect, styled } from "frontity"; import List from "../list"; const Page = ({ state, actions, libraries }) => { // Get information about the current URL. const data = state.source.get(state.router.link); // Get the data of the post. const page = state.source[data.type][data.id]; // ... // Load the page, but only if the data is ready. return data.isReady ? ( <Container> <div className="post-title"> <Title dangerouslySetInnerHTML={{ __html: page.title.rendered }} /> </div> {/* Render the content using the Html2React component so the HTML is processed by the processors we included in the libraries.html2react.processors array. */} <Content> <Html2React html={page.content.rendered} /> </Content> </Container> ) : null; }; // Connect the Page component to get access to the `state` in its `props` export default connect(Page); // Copy styled components from post.js except, DateWrapper const Container = styled.div` width: 90vw; width: clamp(16rem, 93vw, 58rem); margin: 0; padding: 24px; ` // ..
All we did here was remove the meta data from post.js (lines 31-34 and 55-76) and the corresponding styled components. Just as we did with the Mars Theme /list folder, we should export the loadable function in both the pages and posts folders to code split the <List /> component. This way, the <List /> component isn’t displayed if a user is on a single post.
// src/components/pages/index.js import { loadable } from "frontity"; /** Codesplit the list component so it's not included * if the users load a post directly. */ export default loadable(() => import("./page"));
Next, we should update path url of /src/components/index.js component as shown below:
// src/components/index.js import { Global, css, connect, styled, Head } from "frontity"; import Switch from "@frontity/components/switch"; import Header from "./header/header"; import List from "./list"; import Page from "./pages/page"; import Post from "./posts/post"; import Loading from "./loading"; import Title from "./title"; import PageError from "./page-error"; /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> // ... {/* Add some global styles for the whole site */} <Global styles={globalStyles} /> {/* Add the header of the site. */} <HeadContainer> <Header /> </HeadContainer> {/* Add the main section */} <Main> <Switch> <Loading when={data.isFetching} /> <List when={data.isArchive} /> <Page when={data.isPage} /> {/* Added Page component */} <Post when={data.isPostType} /> <PageError when={data.isError} /> </Switch> </Main> </> ); }; export default connect(Theme); // styled components
Now we’re importing the <Page / component and have added our <Main /> styled component.
Let’s move on to our custom footer component. You probably know what to do by now: create a new footer.js component file and drop it into the /src/components/footer/ folder. We can add some widgets to our footer that display the sitemap and some sort of “Powered by” blurb:
This is a super simple example. Please note that I have imported a <Widget /> component (line 4, highlighted above) and called the component (line 9, highlighted above). We don’t actually have a <Widget /> component yet, so let’s make that while we’re at it. That can be a widget.js file in the same directory as the footer, /src/components/footer/.
The default header.js component in Mars Theme is very basic with a site title and site description and navigation items underneath. I wanted to refactor the header component with a site logo and title on the left and the nav.js component (top navigation) on the right.
// src/components/header.js import { connect, styled } from "frontity"; import Link from "./link"; import Nav from "./nav"; import MobileMenu from "./menu"; import logo from "./images/frontity.png" const Header = ({ state }) => { return ( <> <Container> <StyledLink link="/"> {/* Add header logo*/} <Logo src={logo} /> <Title>{state.frontity.title}</Title> </StyledLink> {/*<Description>{state.frontity.description}</Description> */} <Nav /> </Container> <MobileMenu /> </> ); }; // Connect the Header component to get access to the `state` in its `props` export default connect(Header); const Container = styled.div` width: 1000px; // ... `} {/* Logo styled component */} const Logo = styled.img` max-width: 30px; display: inline-block; border-radius: 15px; margin-right: 15px; `; // ...
My refactored header.js component imports a logo image (line 6, highlighted above) and uses in line 14. The nav.js component shown below is basically the same, only with some minor styling modifications.
Adding the <Global> style component
We have already covered the <Global> component and how it’s used for site-wide CSS. There are only a few global styles in the default Mars Theme root component, and I wanted to add more.
I did that with a separate globalStyles file at /src/components/styles/globalStyles.js — similar to Frontity’s Twenty Twenty theme — and added root variables, a CSS reset, and common site-wide element styles, found in the GitHub repo.
Implementing fluid typography
Even though it’s not really in scope, I really wanted to use fluid typography in my custom theme as part of my overall learning journey. So, I added it to the global styles.
It was a fun working with the clamp() function because it meant I could set a range of sizes without any media queries at all!
Adding webfonts to the theme
I also wanted to use a different webfont in my theme. Importing webfonts in CSS using @font-face is covered here on CSS-Tricks. Frontity’s Twenty Twenty Theme uses it, so that’s a good place to reference as well.
We can use the fonts with either with a <link>in the HTML head or with @import in CSS. But Chris covered how to use @font-face with Google Fonts, which allows us to optimize the number of HTTP requests we make since we can download the fonts to our own server.
Those fonts point to a /fonts folder that doesn’t exist. So, let’s make one there and make sure all of the correct font files are in it so the fonts load properly.
Importing globalStyles and @face-font components to the root <Theme /> component
Let’s open our theme root component, /src/components.index.js, and add our globalStyles.js and font-face.js components in there. As shown below, we should import both components into index.js and call the components later.
// src/components/index.js // ... import FontFace from "./styles/font-face"; import globalStyles from "./styles/globalStyles"; /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> // ... {/* Add some global styles for the whole site, like body or a's. * Not classes here because we use CSS-in-JS. Only global HTML tags. */} <Global styles={globalStyles} /> <FontFace /> {/* Add the header of the site. */} // ... export default connect(Theme); {/* delete original globalStyles css component */} // ...
Finally, we should remove mars-theme globalStyles component from index.js. Now our new fonts are applied throughout our project.
Styling pages and posts
Our posts and pages are pretty much styled already, except for some Gutenberg block contents, like buttons, quotes, etc.
To style our post entry meta data, let’s add icons for the author, date, categories, and tags. Frontity’s port of the WordPress Twenty Nineteen theme uses SVG icons and components for author.js, categories.js, posted-on.js and tags.js components, which we can totally copy and use in our own project. I literally copied the top-level entry-meta folder and everything in it from the frontity-twentynineteen theme and added it all to the /components/posts/ project folder.
Next we should update our src/components/list/list-item.js component so we can use the new assets:
// src/components/list/list-item.js import { connect, styled } from "frontity"; import Link from "../link"; import FeaturedMedia from "../featured-media"; // import entry-meta import Author from "../entry-meta/author"; import PostedOn from "../entry-meta/posted-on"; const Item = ({ state, item }) => { return ( <article> <div> {/* If the post has an author, we render a clickable author text. */} <EntryMeta> <Author authorId={item.author} /> {"| "} <PostedOn post={item} /> </EntryMeta> </div> <Link link={item.link}> <Title dangerouslySetInnerHTML={{ __html: item.title.rendered }} /> </Link> // ... </article> ); }; // Connect the Item to gain access to `state` as a prop export default connect(Item);
With these styles in place, our archive page entry meta looks good with icons displayed before entry-meta taxonomy (authors, posted-on).
Here we will modify archives taxonomy page styling with more descriptive header. Let’s update list.js component of our /src/components/list/list.js as shown below.
// src/components/list/list.js import React from "react"; import { connect, styled, decode } from "frontity"; import Item from "./list-item"; import Pagination from "./pagination"; const List = ({ state }) => { // Get the data of the current list. const data = state.source.get(state.router.link); return ( <Container className="entry-content"> {/* If the list is a taxonomy, we render a title. */} {data.isAuthor ? ( <Header> Author Archives:{" "} <PageDescription> {decode(state.source.author[data.id].name)} </PageDescription> </Header> ) : null} {/* If the list is a taxonomy or category, we render a title. */} {data.isTaxonomy || data.isCategory ? ( <Header> {data.taxonomy.charAt(0).toUpperCase() + data.taxonomy.slice(1)}{" "} Archives:{" "} <PageDescription> {decode(state.source[data.taxonomy][data.id].name)} </PageDescription> </Header> ) : null} // ... <Pagination /> </Container> ); }; export default connect(List); const PageDescription = styled.span` font-weight: bold; font-family: var(--body-family); color: var(--color-text); `; // ...
In the example above, we wrapped taxonomy.id data with PageDesctiption styled component applied some styling rules.
The post pagination in the default Mars Theme is very basic with almost no styling. Let’s borrow from the Frontity Twenty Nineteen theme again and add the pagination component and styling from the theme by copying the pagination.js component file in its entirety, and paste it to /src/components/list/pagination.js in our theme.
I added some minor CSS and it works perfectly in our project.
To customize the actual individual posts and pages, let’s make bold header title that’s centered and displays the entry meta:
// src/components/posts/post.js // ... // Import entry-meta import Author from "../entry-meta/author"; import PostedOn from "../entry-meta/posted-on"; import Categories from "../entry-meta/categories"; import Tags from "../entry-meta/tags"; const Post = ({ state, actions, libraries }) => { // ... // Load the post, but only if the data is ready. return data.isReady ? ( <Container className="main"> <div> <Title dangerouslySetInnerHTML={{ __html: post.title.rendered }} /> {/* Hide author and date on pages */} {data.isPost && ( <EntryMeta> <Author authorId={post.author} /> <PostedOn post={post} /> </EntryMeta> )} </div> {/* Look at the settings to see if we should include the featured image */} {state.theme.featured.showOnPost && ( <FeaturedMedia id={post.featured_media} /> )} {data.isAttachment ? ( <div dangerouslySetInnerHTML={{ __html: post.description.rendered }} /> ) : ( <Content> <Html2React html={post.content.rendered} /> {/* Add footer meta-entry */} <EntryFooter> <Categories cats={post.categories} /> <Tags tags={post.tags} /> </EntryFooter> </Content> )} </Container> ) : null; }; export default connect(Post); // ...
Adding Gutenberg block styles
WordPress uses a separate stylesheet for blocks in the Block Editor. Right now, that stylesheet isn’t being used but it would be great if we could get some base styles in there that we use for the various block content we add to pages and posts.
That .wp-block-buttons class is declared in the WordPress blocks stylesheet that we aren’t using… yet.
The WordPress Block Editor uses two styling files: style.css and theme.css. Let’s copy these directly from Frontity’s port of the Twenty Twenty theme because that’s how they implemented the WordPress styles. We can place those inside a /styles/gutenberg/ folder.
“Gutenberg” is the codename that was given to the WordPress Block Editor when it was in development. It’s sometimes still referred to that way.
Let’s add the above two style files to our theme root component, /src/components/index.js, just like we did earlier for globalStyles:
// src/components/index.js import gutenbergStyle from "./styles/gutenberg/style.css"; import gutenbergTheme from "./styles/gutenberg/theme.css"
Here’s our updated <Theme /> root component:
// src/components/index.js // ... import FontFace from "./styles/font-face"; import globalStyles from "./styles/globalStyles"; // Add Gutenberg styles import gutenbergStyle from "./styles/gutenberg/style.css"; import gutenbergTheme from "./styles/gutenberg/theme.css" /** Theme is the root React component of our theme. The one we will export * in roots. */ const Theme = ({ state }) => { // Get information about the current URL. const data = state.source.get(state.router.link); return ( <> // ... {/* Add some global styles for the whole site, like body or a's. * Not classes here because we use CSS-in-JS. Only global HTML tags. */} <Global styles={globalStyles} /> <Global styles={css(gutenbergStyle)} /> <Global styles={css(gutenbergTheme)} /> <FontFace /> {/* Add the header of the site. */} // ... export default connect(Theme); {/* Delete original globalStyles css component */} // ...
We could go about overriding styles many different ways. I went with a simple route. For example, to overriding button styles — .wp-block-buttons — in the styled-component for pages and posts.
We can write override any other block styles the same way. In Frontity’s Twenty Nineteen theme, the entire stylesheet from the WordPress version of the theme is added tothe Frontity version to replicate the exact same appearance. Frontity’s Twenty Twenty port uses only a select few of the styles in the WordPress Twenty Twenty themes, but as inline styles.
Additional styling resources
All the resources we covered in this section on styling are available in the GitHub repository. If you wish to expand my @frontity/labre-theme project further, here are the resources that I gathered.
Comments: The native WordPress functionality for comments are described in this guide.
Infinity Scroll Hooks: This Frontity demo project demonstrates how to use the Infinite Scroll Hooks available in the @frontity/hooks package. Here is a YouTube video that covers it.
Yoast SEO: This is a super popular WordPress plugin and I’m sure many of you would want to use it in Frontity as well. Follow this @frontity/package documentation which automatically gets and renders all of the tags exposed in the REST API by the plugin.
Section 6: Resources and credit
There are ample resources to learn and customize your Frontity project. While preparing this post, I have referred to the following resources extensively. Please refer to original posts for more detailed information.
Frontity documentation and articles
Step-by-step tutorial (Frontity): This is the perfect place to start if you’re new to Frontity, or even if you’ve previously used Frontity and want to level up.
Conceptial guides (Frontity): These guides helps solve some of the common challenges that come up when working with dynamic server-side rendering in React apps connected to WordPress.
Frontity API reference (Frontity). This contains detailed information about Frontity CLI, packages, plugins and themes. Once you’ve mastered the basics of working with Frontity, this is where you’re likely to spend most of your time when working on projects.”
Frontity example repo (Frontity): This is a collection of Frontity projects that demonstrate how Frontity is used in the wild.
Connecting Gutenberg and Frontity (Mario Santos, Frontity product manager) This post is based on Mario’s talk at the 2020 JavaScript for WordPress Conference and has an accompanying video.
Frontity case studies
Moving to Frontity: Diariomotor Case Study (Reyes Martinez): Learn how Frontity helped drive the evolution of Diariomotor, reducing development time and putting them on the path to better performance.
Migrating Aleteia to Frontity (Reyes Martinez). Aleteia is the leading website for Catholic news. Frontity allowed them to move to a modern front-end stack in just a couple of months.
Introducing AWSM F1 Theme for Frontity (Venuraj Varma). Awsm Innovations rebuilt their website with Frontity to boost web performance and deliver a great user experience.
How to Fetch the WordPress Menus in Frontity (Michael Burridge). In this video, Michael explains how to dynamically fetch WordPress menu-items using the WordPress WP-REST-API V2 Menus plugin.
Connecting Gutenberg and Frontity: A Case Study (Mario Santos). In this talk video, Frontity product manager Mario explains how the official Frontity website was rebuilt with both the WordPress Block Editor and Frontity, while highlighting all the challenges and lessons learned along the way.
Frontity has a vibrant and engaging community forum for asking questions or getting help regarding your Frontity project.
Wrapping up and personal thoughts
If you can’t already tell from this post or the others I’ve written, I have a huge passion for headless WordPress sites. As I wrote in a previous article, I came across Frontity through when Chris posted this article. I have been experimenting with it for over six months, choosing to take a deep drive into Frontity and the building blocks used in its default Mars Theme. I must admit that it’s a fascinating software framework and I’ve had an enjoyable learning experience. I may even use this sort of setup for my own personal site!
Here are a few key takeaways from my experience working with Frontity so far:
It’s beginner-friendly and low maintenance: One of the things that impressed me most with Frontity is how relatively easy it is to jump into, even as a beginner. It installs with a couple of commands and takes care of all the setup and configuration for connecting to WordPress via the REST API—something I would have struggled with if left to my own devices.
It works with experimental block themes. In my very limited testing, Frontity’s framework works as expected with experimental block themes, just as it does with classic WordPress themes, like Twenty Twenty. I tested with the Quadrat theme that supports the experimental stuff the Gutenberg team is working on.
Hosting is good, but maybe too expensive: As Chris wrote, Frontity is “a perfect match for Vercel.” However, the current Jamstack pricing model that includes Vercel is unattractive for many ordinary WordPress users.
Frontity’s documentation is good, but could be better: The Frontity team recently reorganized Frontity documentation into tutorials, guides and an API reference. However, in my opinion it’s still confusing for those just getting into the framework.
Because I enjoyed this project so much, I am currently doing a theme project from scratch. Even in WordPress, I learned best by getting my hands dirty building WordPress themes from scratch.
While I am still doing my Gatsby and Frontity side projects, I have not lost my sight from the ongoing WordPress block editor and block-based theme development. At the time of writing, there are already sixteen block-based themes in the WordPress theme directory. I have just started exploring and understanding experimental block themes, which might be another interesting learning project.
After this project, my thoughts about Gatsby, Frontity and the concept of headless sites are still evolving. That’s only because it’s tough to make a fair comparison of when a lot of the tooling is actively in development and changing all the time. There are even experimental themes, that are much lighter and different structural markups than the current PHP-based classic themes, which might be a subject for yet another time.
Please share your experience and thoughts if you have been using Frontity in your projects. As always, I enjoy reading any comments and feedback!
Frontity’s official website itself is a very interesting production-level use case that demonstrates how to successfully link the WordPress Block Editor to Frontity’s framework.
So what I’m going to do is walk you through the steps to create a Frontity site in this article, then follow it up with another article on using and customizing Frontity’s default Mars theme. We’ll start with this post, where we’ll cover the basics of setting up a headless WordPress site on the Frontity framework.
This is not an expert guide but rather a headless WordPress site enthusiast’s journey toward learning the Frontity experience. For a more detailed and authoritative guide, please refer to Frontity’s documentation. frontity doc.
Prerequisites and requirements
Because Frontity is a React-based framework, I’d recommend that you have a working knowledge of React, and JavaScript with ES6 features. Frontity’s tutorial doc details some additional requirements, including:
In the decoupled mode (see below) Frontity fetches REST API data from a WordPress PHP server and returns the final HTML to users as an isomorphic React App (used in the custom theme). In this mode, main domain points to Frontity whereas sub-domain pointing to WordPress site.
In the embedded mode, the Frontity theme package (an Isomorphic React App) replaces the WordPress PHP theme via a required Frontity Embedded Mode plugin. The plugin makes an internal HTTP request to the Frontity/Node.js server to retrieve the HTML pages. In this mode, the main domain points to WordPress where both the site visitors and content editors use the same domain, while Frontity uses the secondary domain (i.e. sub-domain).
Frontity’s built-in AMP feature generates a stripped down version of HTML pages for faster server-side-rendering thus overcoming multiple WordPress requests. It provides a more dynamic static site experience that is fast and has built-in server extedability that could be further improved using a Serverless Pre-redendering (SPR) (also called stale-while-revalidate cache) technique through KeyCDN and StackPath.
To start our project, we need to install a Frontity project site and a WordPress installation for the data source endpoint. In the following sections, we will learn how to set up our Frontity site and connect it to our WordPress installation. The Frontity quick start guide is a very handy step-by-step guide and following guide allows us to set up our Frontity project.
First, check if Node.js and npm is already installed in your machine. If not, download and install them.
#! check node -- version node --version V14.9.0 #! output if installed #! check npm version npm --version 6.14.7 #! output if installed #! to upgrade npm to latest version npm install npm@latest -g
Step 1: Creating a Frontity project
Let’s run the following command using the Frontity CLI to create a new my-frontity project.
### creating a frontity project npx frontity create my-frontity
The above code produces the following output.
Step 2: Select the Frontity mars-theme
Frontity provides two themes, twentytwenty-theme and mars-theme. For starters, Frontity recommends selecting the mars-theme and provides the following output:
If you answer the prompt for e-mail, a valid email address should be entered. I found it useful to enter the email for the first time so I can stay in contact with Frontity developers, but thereafter I didn’t see any use.
Step 3: Frontity project installation
The Frontity server installs the project and its dependencies. If successfully installed, the following output should be displayed:
Step 4: Change directory and restart development server
To get into the project folder, change directory with the following command and start the server to view the newly-created project:
### change dir to project folder cd my-frontity
The Frontity development server can be started with the following command:
### start development server with npx npx frontity dev ### starting dev server with yarn yarn frontity dev
When the development server successfully completes, the project can be viewed at http://localhost:3000 and should display the following screen in the browser:
The above screenshot shows a completed Frontity powered WordPress site front-end with mars-theme. The site is not connected to our own site yet which we will discuss in the next section.
Section 2: WordPress site installation
We need a WordPress site for our data source. We can either use an already installed site or install a fresh test site on your local machine. For this project, I install the latest version of WordPress in my machine with Local and imported theme test data which includes test data for block editor styling as well.
In recent versions of WordPress, the WordPress REST API is built right into WordPress core, so we can check whether it is publicly extending our wp-content data by appending /wp-json to our site URL (e.g. http//mytestsite.local/wp-json). This should return the content in JSON format. Then we are good to proceed.
Select pretty permalinks
One other condition Frontity requires in our WordPress installation is that the pretty permalinks (post name) needs to be activated in Settings > Permalinks.
Section 3: Connecting the Frontity project to WordPress
To connect our WordPress site to frontity project, we should update the frontity.settings.js file:
// change source URL in frontity.settings.js const settings = { ..., packages: [ ..., { name: "@frontity/wp-source", state: { source: { // Change this url to point to your WordPress site. api: "http://frontitytest.local/wp-json" } } } ] }
Please take note that while updating the URL to our WordPress install, we need to change the state.source object name from url to api (highlighted above) and save the file with our updates. Restart the development server, and we will see that the Frontity site is now connected to our own WordPress site.
In the screenshot above, you will notice that the menu items (Nature, Travel, Japan, About Us) are still displayed from the Frontity demo site, which we will fix in the next step.
Step 1: Updating our menu in Frontity site
WordPress treats menus items as private custom post types and are visible only to those who are logged into WordPress. Until the WordPress REST-API Version 2 is released, menu items are not exposed as visible endpoints, but registered menus can be extended using WP-REST-API V2 Menu plugin.
Because menu items are changed infrequently, Frontity Mars theme menu items are often hard-coded in the frontity.settings.js file to be store as state and then exported to the index.js file. For this demo project, I created the WordPress site menu as described in the frontity Mars theme with category and tags.
Next, let’s add our menu items to frontity-settings.js file as described in the Frontity Mars theme.
Let’s save our updates and restart development server as before. We should be able to see menu items (Block, Classic, Alignment, About) from our own site in the header section.
Lines 13-16 define whether we would like to show the featured image on the list (e.g. index page) or on post (e.g. single page).
Step 2: Frontity project folder structure
Our frontity-demo project (we changed project folder name from my-frontity) should contain two files, package.json and frontity.settings.js, and both node_modules/ and packages/mars-theme folders.
A brief descriptions of the files/folders as described in the Frontity doc:
node_modules: where the Frontity project dependencies are installed (aren’t meant to be modified).
packages/ : a folder with mars-theme installed. The theme folder contains src folder which contains custom packages, and maybe some core packages from Frontity that can be edited and customized as desired. Everything in Frontity is a package.
frontity.setiings.js: This is most import file where the basic setup for our app is already populated. Currently these set up are Frontity default but any desired settings and extension are configured in this file. For example, data source URL (e.g. WordPress site URL), and required packages and libraries to run the project are defined under Frontity state package.
package.json: file where the dependencies needed for your app to work are declared.
We’ll get into Frontity theme packages and other dependencies, but in a later article since they warrant a deeper explanation.
Step 3: Modifying styles
Frontity uses the popular CSS-in-JS library Emotion for styling its component. Frontity’s default mars-theme is styled with styled components available from @emotion/syled. Styled components is very similar to CSS. Later in other sections, we will deep-dive into styling frontity project and with a use case example of modifying the entire mars-theme’s styling.
For now let’s do a quick demonstration of changing the color of our site title and description. The header and description styles are defined as Title and Description styled components at the bottom of the header.js component. Now let’s change title color to yellow and the description color to some sort of aqua (left panel). We see the changes reflected in our site header.
Section 4: Deploying the site to Vercel
Frontity lists three popular hosting service providers for hosting a Frontity project, including Vercel, Moovweb XDN, and Heroku. However, in practice it appears that most Frontity projects are hosted at Vercel, as Chris writes, “it’s a perfect match for Vercel.“ Frontity highly recommends Vercel and has prepared a handy step-by-step deployment guide.
Step 1: Create a production version of frontity project
While developing our Frontity project, we develop with the npx frontity dev command. For deployment, we should create a production version of the project from the root of our Frontity project.
#! create production version of project npx frontity build
This createsa build folder “containing both our Frontity project (isomorphic) React app and Frontity (Node.js) server and the content will be used by the command npm frontity serve.”
Step 2: Create an account at Vercel
First, we should create a Vercel account following this signup form, which we can do using our GitHub credentials. We should login from our Frontity projects root folder in the terminal:
#! login to vercel npx vercel login
Step 3: Create vercel.json file
To deploy our site to Vercel, we need the following vercel.json file at the root of our project:
Finally, let’s deploy our project using the vercelcommand from the root of our project folder:
#! deployment vercel npx vercel
Next, we are asked brief deployment-related questions:
Wrapping up
If you have been reading my other articles on WordPress headless sites using Gatsby’s framework, I have had an admirable but frustrating experience, primarily because of my own technical skills to learn and maintain advanced frameworks as a one-man team. Then I came across the Frontity React Framework while reading an article on CSS-Tricks.
As we learned from this and Chris’ article, creating a headless WordPress site with Frontity is pretty simple, all things considered. I am very impressed with its easy setup, streamlined UI, plus it appears to be a better option for less tech-savvy users. For example, you get all of the WordPress content without writing a single query.
In a follow-up article, we will do a deep dive on the default Frontity Mars theme and learn how to customize it to make it our own.
Have you already tried using WordPress headlessly with Gatsby? If you haven’t, you might check this article around the new Gatsby source plugin for WordPress; gatsby-source-wordpress is the official source plugin introduced in March 2021 as a part of the Gatsby 3 release. It significantly improves the integration with WordPress. Also, the WordPress plugin WPGraphQL providing the GraphQL API is now available via the official WordPress repository.
With stable and maintained tools, developing Gatsby websites powered by WordPress becomes easier and more interesting. I got myself involved in this field, I co-founded (with Alexandra Spalato), and recently launched Gatsby WP Themes — a niche marketplace for developers building WordPress-powered sites with Gatsby. In this article, I would love to share my insights and, in particular, discuss the search functionality.
Search does not come out of the box, but there are many options to consider. I will focus on two distinct possibilities — taking advantage of WordPress native search (WordPress search query) vs. using Jetpack Instant Search.
gatsby new gatsby-wordpress-w-search https://github.com/gatsbyjs/gatsby-starter-wordpress-blog
This simple, bare-bone starter creates routes exclusively for individual posts and blog pages. But we can keep it that simple here. Let’s imagine that we don’t want to include pages within the search results.
For the moment, I will leave the WordPress source website as it is and pull the content from the starter author’s WordPress demo. If you use your own source, just remember that there are two plugins required on the WordPress end (both available via the plugin repository):
WPGraphQL – a plugin that runs a GraphQL server on the WordPress instance
WPGatsby – a plugin that modifies the WPGraphQL schema in Gatsby-specific ways (it also adds some mechanism to optimize the build process)
Setting up Apollo Client
With Gatsby, we usually either use the data from queries run on page creation (page queries) or call the useStaticQuery hook. The latter is available in components and does not allow dynamic query parameters; its role is to retrieve GraphQL data at build time. None of those two query solutions works for a user’s-initiated search. Instead, we will ask WordPress to run a search query and send us back the results. Can we send a graphQL search query? Yes! WPGraphQL provides search; you can search posts in WPGraphQL like so:
posts(where: {search: "gallery"}) { nodes { id title content } }
In order to communicate directly with our WPGraphQL API, we will install Apollo Client; it takes care of requesting and caching the data as well as updating our UI components.
yarn add @apollo/client cross-fetch
To access Apollo Client anywhere in our component tree, we need to wrap our app with ApolloProvider. Gatsby does not expose the App component that wraps around the whole application. Instead, it provides the wrapRootElement API. It’s a part of the Gatsby Browser API and needs to be implemented in the gatsby-browser.js file located at the project’s root.
// gatsby-browser.js import React from "react" import fetch from "cross-fetch" import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from "@apollo/client" const cache = new InMemoryCache() const link = new HttpLink({ /* Set the endpoint for your GraphQL server, (same as in gatsby-config.js) */ uri: "https://wpgatsbydemo.wpengine.com/graphql", /* Use fetch from cross-fetch to provide replacement for server environment */ fetch }) const client = new ApolloClient({ link, cache, }) export const wrapRootElement = ({ element }) => ( <ApolloProvider client={client}>{element}</ApolloProvider> )
SearchForm component
Now that we’ve set up ApolloClient, let’s build our Search component.
The SearchResults component receives the searchTerm via props, and that’s where we use Apollo Client.
For each searchTerm, we would like to display the matching posts as a list containing the post’s title, excerpt, and a link to this individual post. Our query will be like so:
const GET_RESULTS = gql` query($ searchTerm: String) { posts(where: { search: $ searchTerm }) { edges { node { id uri title excerpt } } } } `
We will use the useQuery hook from @apollo-client to run the GET_RESULTS query with a search variable.
The useQuery hook returns an object that contains loading, error, and data properties. We can render different UI elements according to the query’s state. As long as loading is truthy, we display <p>Searching posts...</p>. If loading and error are both falsy, the query has completed and we can loop over the data.posts.edges and display the results.
if (loading) return <p>Searching posts...</p> if (error) return <p>Error - {error.message}</p> // else return ( //... )
For the moment, I am adding the <Search /> to the layout component. (I’ll move it somewhere else a little bit later.) Then, with some styling and a visible state variable, I made it feel more like a widget, opening on click and fixed-positioned in the top right corner.
Paginated queries
Without the number of entries specified, the WPGraphQL posts query returns ten first posts; we need to take care of the pagination. WPGraphQL implements the pagination following the Relay Specification for GraphQL Schema Design. I will not go into the details; let’s just note that it is a standardized pattern. Within the Relay specification, in addition to posts.edges (which is a list of { cursor, node } objects), we have access to the posts.pageInfo object that provides:
endCursor – cursor of the last item in posts.edges,
startCursor – cursor of the first item in posts.edges,
hasPreviousPage – boolean for “are there more results available (backward),” and
hasNextPage – boolean for “are there more results available (forward).”
We can modify the slice of the data we want to access with the additional query variables:
first – the number of returned entries
after – the cursor we should start after
How do we deal with pagination queries with Apollo Client? The recommended approach is to use the fetchMore function, that is (together with loading, error and data) a part of the object returned by the useQuery hook.
// src/components/search-results.js import React from "react" import { Link } from "gatsby" import { useQuery, gql } from "@apollo/client" const GET_RESULTS = gql` query($ searchTerm: String, $ after: String) { posts(first: 10, after: $ after, where: { search: $ searchTerm }) { edges { node { id uri title } } pageInfo { hasNextPage endCursor } } } ` const SearchResults = ({ searchTerm }) => { const { data, loading, error, fetchMore } = useQuery(GET_RESULTS, { variables: { searchTerm, after: "" }, }) if (loading && !data) return <p>Searching posts for {searchTerm}...</p> if (error) return <p>Error - {error.message}</p> const loadMore = () => { fetchMore({ variables: { after: data.posts.pageInfo.endCursor, }, // with notifyOnNetworkStatusChange our component re-renders while a refetch is in flight so that we can mark loading state when waiting for more results (see lines 42, 43) notifyOnNetworkStatusChange: true, }) } return ( <section className="search-results"> {/* as before */} {data.posts.pageInfo.hasNextPage && ( <button type="button" onClick={loadMore} disabled={loading}> {loading ? "Loading..." : "More results"} </button> )} </section> ) } export default SearchResults
The first argument has its default value but is necessary here to indicate that we are sending a paginated request. Without first, pageInfo.hasNextPage will always be false, no matter the search keyword.
Calling fetchMore fetches the next slice of results but we still need to tell Apollo how it should merge the “fetch more” results with the existing cached data. We specify all the pagination logic in a central location as an option passed to the InMemoryCache constructor (in the gatsby-browser.js file). And guess what? With the Relay specification, we’ve got it covered — Apollo Client provides the relayStylePagination function that does all the magic for us.
// gatsby-browser.js import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider } from "@apollo/client" import { relayStylePagination } from "@apollo/client/utilities" const cache = new InMemoryCache({ typePolicies: { Query: { fields: { posts: relayStylePagination(["where"]), }, }, }, }) /* as before */
Just one important detail: we don’t paginate all posts, but instead the posts that correspond to a specific where condition. Adding ["where"] as an argument to relayStylePagination creates a distinct storage key for different search terms.
Making search persistent
Right now my Search component lives in the Layout component. It’s displayed on every page but gets unmounted every time the route changes. What if we could keep the search results while navigating? We can take advantage of the Gatsby wrapPageElement browser API to set persistent UI elements around pages.
Let’s move <Search /> from the layout component to the wrapPageElement:
// gatsby-browser.js import Search from "./src/components/search" /* as before */ export const wrapPageElement = ({ element }) => { return <><Search />{element}</> }
The APIs wrapPageElement and wrapRootElement exist in both the browser and Server-Side Rendering (SSR) APIs. Gatsby recommends that we implement wrapPageElement and wrapRootElement in both gatsby-browser.js and gatsby-ssr.js. Let’s create the gatsby-ssr.js (in the root of the project) and re-export our elements:
// gatsby-ssr.js export { wrapRootElement, wrapPageElement } from "./gatsby-browser"
The wrapPageElement approach may not be ideal in all cases. Our search widget is “detached” from the layout component. It works well with the position “fixed” like in our working example or within an off-canvas sidebar like in this Gatsby WordPress theme.
But what if you want to have “persistent” search results displayed within a “classic” sidebar? In that case, you could move the searchTerm state from the Search component to a search context provider placed within the wrapRootElement:
// gatsby-browser.js import SearchContextProvider from "./src/search-context" /* as before */ export const wrapRootElement = ({ element }) => ( <ApolloProvider client={client}> <SearchContextProvider> {element} </SearchContextProvider> </ApolloProvider> )
You can see it in action in another Gatsby WordPress theme:
Note how, since Apollo Client caches the search results, we immediately get them on the route change.
Results from posts and pages
If you checked the theme examples above, you might have noticed how I deal with querying more than just posts. My approach is to replicate the same logic for pages and display results for each post type separately.
Alternatively, you could use the Content Node interface to query nodes of different post types in a single connection:
const GET_RESULTS = gql` query($ searchTerm: String, $ after: String) { contentNodes(first: 10, after: $ after, where: { search: $ searchTerm }) { edges { node { id uri ... on Page { title } ... on Post { title excerpt } } } pageInfo { hasNextPage endCursor } } } `
Going beyond the default WordPress search
Our solution seems to work but let’s remember that the underlying mechanism that actually does the search for us is the native WordPress search query. And the WordPress default search function isn’t great. Its problems are limited search fields (in particular, taxonomies are not taken into account), no fuzzy matching, no control over the order of results. Big websites can also suffer from performance issues — there is no prebuilt search index, and the search query is performed directly on the website SQL database.
There are a few WordPress plugins that enhance the default search. Plugins like WP Extended Search add the ability to include selected meta keys and taxonomies in search queries.
The Relevanssi plugin replaces the standard WordPress search with its search engine using the full-text indexing capabilities of the database. Relevanssi deactivates the default search query which breaks the WPGraphQL where: {search : …}. There is some work already done on enabling Relevanssi search through WPGraphQL; the code might not be compatible with the latest WPGraphQL version, but it seems to be a good start for those who opt for Relevanssi search.
In the second part of this article, we’ll take one more possible path and have a closer look at the premium service from Jetpack — an advanced search powered by Elasticsearch. By the way, Jetpack Instant search is the solution adopted by CSS-Tricks.
Using Jetpack Instant Search with Gatsby
Jetpack Search is a per-site premium solution by Jetpack. Once installed and activated, it will take care of building an Elasticsearch index. The search queries no longer hit the SQL database. Instead, the search query requests are sent to the cloud Elasticsearch server, more precisely to:
There are a lot of search parameters to specify within the URL above. In our case, we will add the following:
filter[bool][must][0][term][post_type]=post: We only need results that are posts here, simply because our Gatsby website is limited to post. In real-life use, you might need spend some time configuring the boolean queries.
size=10 sets the number of returned results (maximum 20).
with highlight_fields[0]=title, we get the title string (or a part of it) with the searchTerm within the <mark> tags.
highlight_fields[0]=content is the same as below but for the post’s content.
There are three more search parameters depending on the user’s action:
query: The search term from the search input, e.g. gallery
sort: how the results should be orderer, the default is by score "score_default" (relevance) but there is also "date_asc" (newest) and "date_desc" (oldest)
page_handle: something like the “after” cursor for paginated results. We only request 10 results at once, and we will have a “load more” button.
Now, let’s see how a successful response is structured:
{ total: 9, corrected_query: false, page_handle: false, // or a string it the total value > 10 results: [ { _score: 196.51814, fields: { date: '2018-11-03 03:55:09', 'title.default': 'Block: Gallery', 'excerpt.default': '', post_id: 1918, // we can configure what fields we want to add here with the query search parameters }, result_type: 'post', railcar: {/* we will not use this data */}, highlight: { title: ['Block: <mark>Gallery</mark>'], content: [ 'automatically stretch to the width of your <mark>gallery</mark>. ... A four column <mark>gallery</mark> with a wide width:', '<mark>Gallery</mark> blocks have two settings: the number of columns, and whether or not images should be cropped', ], }, }, /* more results */ ], suggestions: [], // we will not use suggestions here aggregations: [], // nor the aggregations }
The results field provides an array containing the database post IDs. To display the search results within a Gatsby site, we need to extract the corresponding post nodes (in particular their uri ) from the Gatsby data layer. My approach is to implement an instant search with asynchronous calls to the rest API and intersect the results with those of the static GraphQL query that returns all post nodes.
Let’s start by building an instant search widget that communicates with the search API. Since this is not specific to Gatsby, let’s see it in action in this Pen:
Here, useDebouncedInstantSearch is a custom hook responsible for fetching the results from the Jetpack Search API. My solution uses the awesome-debounce-promise library that allows us to take some extra care of the fetching mechanism. An instant search responds to the input directly without waiting for an explicit “Go!” from the user. If I’m typing fast, the request may change several times before even the first response arrives. Thus, there might be some unnecessary network bandwidth waste. The awesome-debounce-promise waits a given time interval (say 300ms) before making a call to an API; if there is a new call within this interval, the previous one will never be executed. It also resolves only the last promise returned from the call — this prevents the concurrency issues.
Now, with the search results available, let’s move back to Gatsby and build another custom hook:
import {useStaticQuery, graphql} from "gatsby" export const useJetpackSearch = (params) => { const { allWpPost: { nodes }, } = useStaticQuery(graphql` query AllPostsQuery { allWpPost { nodes { id databaseId uri title excerpt } } } `) const { error, loading, data } = useDebouncedInstantSearch(params) return { error, loading, data: { ...data, // map the results results: data.results.map(el => { // for each result find a node that has the same databaseId as the result field post_id const node = nodes.find(item => item.databaseId === el.fields.post_id) return { // spread the node ...node, // keep the highlight info highlight: el.highlight } }), } } }
I will call the useJetpackSearch within <SearchResults />. The Gatsby-version of <SearchResults /> is almost identical as that in the Pen above. The differences are highlighted in the code block below. The hook useDebouncedInstantSearch is replaced by useJetpackSearch (that calls the former internally). There is a Gatsby Link that replaces h2 as well as el.fields["title.default"] and el.fields["excerpt.default"] are replaced by el.title and el.excerpt.
You can find the complete code in this repo and see it in action in this demo. Note that I no longer source WordPress data from the generic WordPress demo used by Gatsby starter. I need to have a website with Jetpack Search activated.
Wrapping up
We’ve just seen two ways of dealing with search in headless WordPress. Besides a few Gatsby-specific technical details (like using Gatsby Browser API), you can implement both discussed approaches within other frameworks. We’ve seen how to make use of the native WordPress search. I guess that it is an acceptable solution in many cases.
But if you need something better, there are better options available. One of them is Jetpack Search. Jetpack Instant Search does a great job on CSS-Tricks and, as we’ve just seen, can work with headless WordPress as well. There are probably other ways of implementing it. You can also go further with the query configuration, the filter functionalities, and how you display the results.
I wonder where headless WordPress will land. And by “headless” I mean only using the WordPress admin and building out the user-facing site through the WordPress REST API rather than the traditional WordPress theme structure.
Is it… big? The future of WordPress? Or relatively niche?
Where’s the demand?
Certainly, there is demand for it. I know plenty of people doing it. For instance, Gatsby has a gatsby-source-wordpress plugin that allows you to source content from a WordPress site in a way that consumes the WordPress REST API and caches it as GraphQL for use in a React-powered Gatsby site. It has been downloaded 59k times this month and 851k times overall, as I write. That’s a healthy chunk of usage for one particular site-building technology. Every use case there is technically using WordPress headless-ly. If you’re interested in this, here’s Ganesh Dahaldigging deep into it.
What is going headless and improve to?
The Gatsby integration makes a solid case for why anyone would consider a headless WordPress site. I’ll get to that.
Many advocate the main reason is architectural. It de-couples the back end from the front end. It tears down the monolith. As a de-coupled system, the back and front ends can evolve independently. And yet, I’m less hot on that idea as years go by. For example, I’d argue that the chances of simply ripping out WordPress and replace it with another CMS in a headless setup like this is easier said than done. Also, the idea that I’m going to use the WordPress API not just to power a website, but also a native reading app, and some digital internet-connected highway billboard or something is not a use case that’s exploding in popularity as far as I can tell.
The real reason I think people reach for a WordPress back end for a Gatsby-driven front end is essentially this: they like React. They like building things from components. They like the fast page transitions. They like being able to host things somewhere Jamstack-y with all the nice developer previews and such. They like the hot module reloading. They like Prettier and JSX. They just like it, and I don’t blame them. When you enjoy that kind of developer experience, going back to authoring PHP templates where it takes manually refreshing the browser and maintaining some kind of hand-rolled build process isn’t exactly enticing.
Frontity is another product looking to hone in on React + WordPress. Geoff and Sarah shared how to do all this last year on the Vue/Nuxt side.
But will headless WordPress become more popular than the traditional theming model of WordPress that’s based on PHP templates that align to an explicit structure? Nah. Well, maybe it would if WordPress itself champions the idea and offers guidance, training, and documentation that make it easier for developers to adopt that approach. I’d buy into it if WordPress says that a headless architecture is the new course of direction. But none of those things are true. Consequently, to some degree, it’s a niche thing.
Just how niche is headless?
WP Engine is a big WordPress host and has a whole thing called Atlas. And that effort definitely looks like they are taking this niche market seriously. I’m not 100% sure what Atlas all is, but it looks like a dashboard for spinning up sites with some interesting looking code-as-config. One of the elephants in the room with headless WordPress is that, well, now you have two websites to deal with. You have wherever you are hosting and running WordPress, and wherever you are hosting and running the site that consumes the WordPress APIs. Maybe this brings those two things together somehow. The deploy-from-Git-commits thing is appealing and what I’d consider table stakes for modern hosting these days.
Another reason people are into headless WordPress is that the end result can be static, as in, pre-generated HTML pages. That means the server for your website can be highly CDN-ized, as it’s literally only static assets that are instantly available to download. There’s no PHP or database for server-side rendering things, which can be slow (and, to be fair, dealt with) since it adds a process ahead of rendering.
What’s “the WordPress way” for going headless?
I’d lump any service that builds a static version of your WordPress site into the headless WordPress bucket. That’s because, ultimately, those sites are using WordPress APIs to build those static files, just like Gatsby or whatever else would do.
That’s what Strattic does. They spin up a WordPress site for you that they consider staging. You do your WordPress work there, then use their publish system to push a static version of your site to production. That’s compelling because it solves for something that other headless WordPress usage doesn’t: just doing things the WordPress way.
For example, custom WordPress blocks or plugins might produce output that not only contains custom HTML, but CSS and JavaScript as well. Imagine a “carousel” block or plugin. That carousel isn’t going to work if all you’re doing is grabbing the post content from an API and dunking it onto a page. You’ll either need to go extract the CSS and JavaScript from elsewhere and include it, or somehow just know that isn’t how you do things anymore. You might attach the images as metadata somehow, pull them client-side, and do your own implementation of a carousel. With Strattic, theoretically, it’ll work as the HTML, CSS, and JavaScript is still present on the static site. But notably, you don’t have PHP, so Strattic had to hand-build form integrations, they use client-side Algolia for search, Disqus for comments, etc., because there is no server-side language available.
Shifter is another player here. It’s similar to Strattic where you work on your site in the WordPress admin, then publish to a static site. I believe Shifter even spins your WordPress site down when it’s not in use, which makes sense since the output is static and there is no reason a server with PHP and MySQL needs to be running. As an alternative, Shifter has a headless-only WordPress setup that presumably stays spun up all the time for outside API usage.
It’s fun to think about all this stuff
But as I do, I realize that the ideas and discussions around headless WordPress are mostly focused on the developer. WordPress has this huge market of people who just are not developers. Yet, they administer a WordPress site, taking advantage of the plugin and theme ecosystem. That’s kinda cool, and it’s impressive that WordPress serves both markets so well. There’s just a heck of a lot more WordPress site owners who aren’t developers than those who are, I reckon, so that alone will keep headless WordPress from being anything more than a relatively niche concept for some time. But, ya know, if they wanna put GraphQL in core, I’ll still take it kthxbye.
This article introduces the concept of the headless CMS, a backend-only content management system that allows developers to create, store, manage and publish the content over an API using the Fauna and Vercel functions. This improves the frontend-backend workflow, that enables developers to build excellent user experience quickly.
In this tutorial, we will learn and use headless CMS, Fauna, and Vercel functions to build a blogging platform, Blogify🚀. After that, you can easily build any web application using a headless CMS, Fauna and Vercel functions.
Introduction
According to MDN, A content management system (CMS) is a computer software used to manage the creation and modification of digital content. CMS typically has two major components: a content management application (CMA), as the front-end user interface that allows a user, even with limited expertise, to add, modify, and remove content from a website without the intervention of a webmaster; and a content delivery application (CDA), that compiles the content and updates the website.
The Pros And Cons Of Traditional vs Headless CMS
Choosing between these two can be quite confusing and complicated. But they both have potential advantages and drawbacks.
Traditional CMS Pros
Setting up your content on a traditional CMS is much easier as everything you need (content management, design, etc) are made available to you.
A lot of traditional CMS has drag and drop, making it easy for a person with no programming experience to work easily with them. It also has support for easy customization with zero to little coding knowledge.
Traditional CMS Cons
The plugins and themes which the traditional CMS relies on may contain malicious codes or bugs and slow the speed of the website or blog.
The traditional coupling of the front-end and back-end definitely would more time and money for maintenance and customization.
Headless CMS Pros
There’s flexibility with choice of frontend framework to use since the frontend and backend are separated from each other, it makes it possible for you to pick which front-end technology suits your needs. It gives the freewill to choose the tools need to build the frontend—flexibility during the development stage.
Deploying works easier with headless CMS. The applications (blogs, websites, etc) built with headless CMS can be easily be deployed to work on various displays such as web device, mobile devices, AR/VR devices.
Headless CMS Cons
You are left with the worries of managing your back-end infrastructures, setting up the UI component of your site, app.
Implementation of headless CMS are known to be more costly against the traditional CMS. Building headless CMS application that embodies analytics are not cost-effective.
Fauna uses a preexisting infrastructure to build web applications without the usually setting up a custom API server. This efficiently helps to save time for developers, and the stress of choosing regions and configuring storage that exists among other databases; which is global/multi-region by default, are nonexistent with Fauna. All maintenance we need are actively taken care of by engineers and automated DevOps at Fauna. We will use Fauna as our backend-only content management system.
Pros Of Using Fauna
The ease to use and create a Fauna database instance from within development environment of the hosting platforms like Netlify or Vercel.
Great support for querying data via GraphQL or use Fauna’s own query language. Fauna Query Language (FQL), for complex functions.
Access data in multiple models including relational, document, graph and temporal.
Capabilities like built-in authentication, transparent scalability and multi-tenancy are fully available on Fauna.
Add-on through Fauna Console as well as Fauna Shell makes it easy to manage database instance very easily.
Vercel Functions, also known as Serverless Functions, according to the docs are pieces of code written with backend languages that take an HTTP request and provide a response.
Prerequisites
To take full advantage of this tutorial, ensure the following tools are available or installed on your local development environment:
Access to Fauna dashboard
Basic knowledge of React and React Hooks
Have create-react-app installed as a global package or use npx to bootstrap the project.
Node.js version >= 12.x.x installed on your local machine.
Ensure that npm or yarn is also installed as a package manager
Database Setup With Fauna
Sign in into your fauna account to get started with Fauna, or first register a new account using either email credentials/details or using an existing Github account as a new user. You can register for a new account here. Once you have created a new account or signed in, you are going to be welcomed by the dashboard screen. We can also make use of the fauna shell if you love the shell environment. It easily allows you to create and/or modify resources on Fauna through the terminal.
But we will use the website throughout this tutorial. Once signed in, the dashboard screen welcomes you:
Now we are logged in or have our accounts created, we can go ahead to create our Fauna. We’ll go through following simple steps to create the new fauna database using Fauna services. We start with naming our database, which we’ll use as our content management system. In this tutorial, we will name our database blogify.
With the database created, next step is to create a new data collection from the Fauna dashboard. Navigate to the Collection tab on the side menu and create a new collection by clicking on the NEW COLLECTION button.
We’ll then go ahead to give whatever name well suiting to our collection. Here we will call it blogify_posts.
Next step in getting our database ready is to create a new index. Navigate to the Indexes tab to create an index. Searching documents in Fauna can be done by using indexes, specifically by matching inputs against an index’s terms field. Click on the NEW INDEX button to create an index. Once in create index screen, fill out the form: selecting the collection we’ve created previously, then giving a name to our index. In this tutorial, we will name ours all_posts. We can now save our index.
After creating an index, now it’s time to create our DOCUMENT, this will contain the contents/data we want to use for our CMS website. Click on the NEW DOCUMENT button to get started. With the text editor to create our document, we’ll create an object data to serve our needs for the website.
The above post object represents the unit data we need to create our blog post. Your choice of data can be so different from what we have here, serving the purpose whatever you want it for within your website. You can create as much document you may need for your CMS website. To keep things simple, we just have three blog posts.
Now that we have our database setup complete to our choice, we can move on to create our React app, the frontend.
Create A New React App And Install Dependencies
For the frontend development, we will need dependencies such as Fauna SDK, styled-components and vercel in our React app. We will use the styled-components for the UI styling, use the vercel within our terminal to host our application. The Fauna SDK would be used to access our contents at the database we had setup. You can always replace the styled-components for whatever library you decide to use for your UI styling. Also use any UI framework or library you preferred to others.
npx create-react-app blogify # install dependencies once directory is done/created yarn add fauna styled-components # install vercel globally yarn global add vercel
The fauna package is Fauna JavaScript driver for Fauna. The library styled-components allows you to write actual CSS code to style your components. Once done with all the installation for the project dependencies, check the package.json file to confirm all installation was done successfully.
Now let’s start an actual building of our blog website UI. We’ll start with the header section. We will create a Navigation component within the components folder inside the src folder, src/components, to contain our blog name, Blogify🚀.
After being imported within the App components, the above code coupled with the stylings through the styled-components library, will turn out to look like the below UI:
Now time to create the body of the website, that will contain the post data from our database. We structure a component, called Posts, which will contains our blog posts created on the backend.
The above code contains styles for JSX that we’ll still create once we start querying for data from the backend to the frontend.
Integrate Fauna SDK Into Our React App
To integrate the fauna client with the React app, you have to make an initial connection from the app. Create a new file db.js at the directory path src/config/. Then import the fauna driver and define a new client. The secret passed as the argument to the fauna.Client() method is going to hold the access key from .env file:
Inside the Posts component create a state variable called posts using useState React Hooks with a default value of an array. It is going to store the value of the content we’ll get back from our database using the setPosts function. Then define a second state variable, visible, with a default value of false, that we’ll use to hide or show more post content using the handleDisplay function that would be triggered by a button we’ll add later in the tutorial.
Since our blog website is going to perform only one operation, that’s to get the data/contents we created on the database, let’s create a new directory called src/api/ and inside it, we create a new file called index.js. Making the request with ES6, we’ll use import to import the client and the query instance from the config/db.js file:
The query above to the database is going to return a ref that we can map over to get the actual results need for the application. We’ll make sure to append the catch that will help check for an error while querying the database, so we can log it out.
Next is to display all the data returned from our CMS, database—from the Fauna collection. We’ll do so by invoking the query getAllPosts from the ./api/index.js file inside the useEffect Hook inside our Posts component. This is because when the Posts component renders for the first time, it iterates over the data, checking if there are any post in the database:
Open the browser’s console to inspect the data returned from the database. If all things being right, and you’re closely following, the return data should look like the below:
With these data successfully returned from the database, we can now complete our Posts components, adding all necessary JSX elements that we’ve styled using styled-components library. We’ll use JavaScript map to loop over the posts state, array, only when the array is not empty:
With the complete code structure above, our blog website, Blogify🚀, will look like the below UI:
Deploying To Vercel
Vercel CLI provides a set of commands that allow you to deploy and manage your projects. The following steps will get your project hosted from your terminal on vercel platform fast and easy:
vercel login
Follow the instructions to login into your vercel account on the terminal
vercel
Using the vercel command from the root of a project directory. This will prompt questions that we will provide answers to depending on what’s asked.
vercel ? Set up and deploy “~/Projects/JavaScript/React JS/blogify”? [Y/n] ? Which scope do you want to deploy to? ikehakinyemi ? Link to existing project? [y/N] n ? What’s your project’s name? (blogify) # click enter if you don't want to change the name of the project ? In which directory is your code located? ./ # click enter if you running this deployment from root directory ? ? Want to override the settings? [y/N] n
This will deploy your project to vercel. Visit your vercel account to complete any other setup needed for CI/CD purpose.
Conclusion
I’m glad you followed the tutorial to this point, hope you’ve learnt how to use Fauna as Headless CMS. The combination of Fauna with Headless CMS concepts you can build great web application, from e-commerce application to Notes keeping application, any web application that needs data to be stored and retrieved for use on the frontend. Here’s the GitHub link to code sample we used within our tutorial, and the live demo which is hosted on vercel.
If you’re building a WordPress site, you need a good reason not to choose a WordPress form plugin. They are convenient and offer plenty of customizations that would take a ton of effort to build from scratch. They render the HTML, validate the data, store the submissions, and provide integration with third-party services.
But suppose we plan to use WordPress as a headless CMS. In this case, we will be mainly interacting with the REST API (or GraphQL). The front-end part becomes our responsibility entirely, and we can’t rely anymore on form plugins to do the heavy lifting in that area. Now we’re in the driver’s seat when it comes to the front end.
Forms were a solved problem, but now we have to decide what to do about them. We have a couple of options:
Do we use our own custom API if we have such a thing? If not, and we don’t want to create one, we can go with a service. There are many good static form providers, and new ones are popping up constantly.
Can we keep using the WordPress plugin we already use and leverage its validation, storage, and integration?
The most popular free form plugin, Contact Form 7, has a submission REST API endpoint, and so does the well-known paid plugin, Gravity Forms, among others.
From a technical standpoint, there’s no real difference between submitting the form‘s data to an endpoint provided by a service or a WordPress plugin. So, we have to decide based on different criteria. Price is an obvious one; after that is the availability of the WordPress installation and its REST API. Submitting to an endpoint presupposes that it is always available publicly. That’s already clear when it comes to services because we pay for them to be available. Some setups might limit WordPress access to only editing and build processes. Another thing to consider is where you want to store the data, particularly in a way that adheres to GPDR regulations.
When it comes to features beyond the submission, WordPress form plugins are hard to match. They have their ecosystem, add-ons capable of generating reports, PDFs, readily available integration with newsletters, and payment services. Few services offer this much in a single package.
Even if we use WordPress in the “traditional” way with the front end based on a WordPress theme, using a form plugin’s REST API might make sense in many cases. For example, if we are developing a theme using a utility-first CSS framework, styling the rendered form with fixed markup structured with a BEM-like class convention leaves a sour taste in any developer’s mouth.
The purpose of this article is to present the two WordPress form plugins submission endpoints and show a way to recreate the typical form-related behaviors we got used to getting out of the box. When submitting a form, in general, we have to deal with two main problems. One is the submission of the data itself, and the other is providing meaningful feedback to the user.
So, let’s start there.
The endpoints
Submitting data is the more straightforward part. Both endpoints expect a POST request, and the dynamic part of the URL is the form ID.
Contact Form 7 REST API is available immediately when the plugin is activated, and it looks like this:
The Gravity Forms REST API is disabled by default. To enable it, we have to go to the plugin’s settings, then to the REST API page, and check the “Enable access to the API” option. There is no need to create an API key, as the form submission endpoint does not require it.
The body of the request
Our example form has five fields with the following rules:
a required text field
a required email field
a required date field that accepts dates before October 4, 1957
an optional textarea
a required checkbox
For Contact Form 7’s request’s body keys, we have to define them with the form-tags syntax:
Gravity Forms expects the keys in a different format. We have to use an auto-generated, incremental field ID with the input_ prefix. The ID is visible when you are editing the field.
We can save ourselves a lot of work if we use the expected keys for the inputs’ name attributes. Otherwise, we have to map the input names to the keys.
Putting everything together, we get an HTML structure like this for Contact Form 7:
<form action="https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback" method="post"> <label for="somebodys-name">Somebody's name</label> <input id="somebodys-name" type="text" name="somebodys-name"> <!-- Other input elements --> <button type="submit">Submit</button> </form>
In the case of Gravity Forms, we only need to switch the action and the name attributes:
<form action="https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions" method="post"> <label for="input_1">Somebody's name</label> <input id="input_1" type="text" name="input_1"> <!-- Other input elements --> <button type="submit">Submit</button> </form>
Since all the required information is available in the HTML, we are ready to send the request. One way to do this is to use the FormData in combination with the fetch:
const formSubmissionHandler = (event) => { event.preventDefault(); const formElement = event.target, { action, method } = formElement, body = new FormData(formElement); fetch(action, { method, body }) .then((response) => response.json()) .then((response) => { // Determine if the submission is not valid if (isFormSubmissionError(response)) { // Handle the case when there are validation errors } // Handle the happy path }) .catch((error) => { // Handle the case when there's a problem with the request }); }; const formElement = document.querySelector("form"); formElement.addEventListener("submit", formSubmissionHandler);
We can send the submission with little effort, but the user experience is subpar, to say the least. We owe to users as much guidance as possible to submit the form successfully. At the very least, that means we need to:
show a global error or success message,
add inline field validation error messages and possible directions, and
draw attention to parts that require attention with special classes.
Field validation
On top of using built-in HTML form validation, we can use JavaScript for additional client-side validation and/or take advantage of server-side validation.
When it comes to server-side validation, both Contact Form 7 and Gravity Forms offer that out of the box and return the validation error messages as part of the response. This is convenient as we can control the validation rules from the WordPress admin.
For more complex validation rules, like conditional field validation, it might make sense to rely only on the server-side because keeping the front-end JavaScript validation in sync with the plugins setting can become a maintenance issue.
If we solely go with the server-side validation, the task becomes about parsing the response, extracting the relevant data, and DOM manipulation like inserting elements and toggle class-names.
Response messages
The response when there is a validation error for Contact Form 7 look like this:
{ "into": "#", "status": "validation_failed", "message": "One or more fields have an error. Please check and try again.", "posted_data_hash": "", "invalid_fields": [ { "into": "span.wpcf7-form-control-wrap.somebodys-name", "message": "The field is required.", "idref": null, "error_id": "-ve-somebodys-name" }, { "into": "span.wpcf7-form-control-wrap.any-email", "message": "The field is required.", "idref": null, "error_id": "-ve-any-email" }, { "into": "span.wpcf7-form-control-wrap.before-space-age", "message": "The field is required.", "idref": null, "error_id": "-ve-before-space-age" }, { "into": "span.wpcf7-form-control-wrap.fake-terms", "message": "You must accept the terms and conditions before sending your message.", "idref": null, "error_id": "-ve-fake-terms" } ] }
On successful submission, the response looks like this:
{ "into": "#", "status": "mail_sent", "message": "Thank you for your message. It has been sent.", "posted_data_hash": "d52f9f9de995287195409fe6dcde0c50" }
Compared to this, Gravity Forms’ validation error response is more compact:
{ "is_valid": false, "validation_messages": { "1": "This field is required.", "2": "This field is required.", "3": "This field is required.", "5": "This field is required." }, "page_number": 1, "source_page_number": 1 }
But the response on a successful submission is bigger:
{ "is_valid": true, "page_number": 0, "source_page_number": 1, "confirmation_message": "<div id='gform_confirmation_wrapper_1' class='gform_confirmation_wrapper '><div id='gform_confirmation_message_1' class='gform_confirmation_message_1 gform_confirmation_message'>Thanks for contacting us! We will get in touch with you shortly.</div></div>", "confirmation_type": "message" }
While both contain the information we need, they don‘t follow a common convention, and both have their quirks. For example, the confirmation message in Gravity Forms contains HTML, and the validation message keys don’t have the input_ prefix — the prefix that’s required when we send the request. On the other side, validation errors in Contact Form 7 contain information that is relevant only to their front-end implementation. The field keys are not immediately usable; they have to be extracted.
In a situation like this, instead of working with the response we get, it’s better to come up with a desired, ideal format. Once we have that, we can find ways to transform the original response to what we see fit. If we combine the best of the two scenarios and remove the irrelevant parts for our use case, then we end up with something like this:
{ "isSuccess": false, "message": "One or more fields have an error. Please check and try again.", "validationError": { "somebodys-name": "This field is required.", "any-email": "This field is required.", "input_3": "This field is required.", "input_5": "This field is required." } }
And on successful submission, we would set isSuccess to true and return an empty validation error object:
{ "isSuccess": true, "message": "Thanks for contacting us! We will get in touch with you shortly.", "validationError": {} }
Now it’s a matter of transforming what we got to what we need. The code to normalize the Contact Forms 7 response is this:
const normalizeContactForm7Response = (response) => { // The other possible statuses are different kind of errors const isSuccess = response.status === 'mail_sent'; // A message is provided for all statuses const message = response.message; const validationError = isSuccess ? {} : // We transform an array of objects into an object Object.fromEntries( response.invalid_fields.map((error) => { // Extracts the part after "cf7-form-control-wrap" const key = /cf7[-a-z]*.(.*)/.exec(error.into)[1]; return [key, error.message]; }) ); return { isSuccess, message, validationError, }; };
The code to normalize the Gravity Forms response winds up being this:
const normalizeGravityFormsResponse = (response) => { // Provided already as a boolean in the response const isSuccess = response.is_valid; const message = isSuccess ? // Comes wrapped in a HTML and we likely don't need that stripHtml(response.confirmation_message) : // No general error message, so we set a fallback 'There was a problem with your submission.'; const validationError = isSuccess ? {} : // We replace the keys with the prefixed version; // this way the request and response matches Object.fromEntries( Object.entries( response.validation_messages ).map(([key, value]) => [`input_$ {key}`, value]) ); return { isSuccess, message, validationError, }; };
We are still missing a way to display the validation errors, success messages, and toggling classes. However, we have a neat way of accessing the data we need, and we removed all of the inconsistencies in the responses with a light abstraction. When put together, it’s ready to be dropped into an existing codebase, or we can continue building on top of it.
There are many ways to tackle the remaining part. What makes sense will depend on the project. For situations where we mainly have to react to state changes, a declarative and reactive library can help a lot. Alpine.js was covered here on CSS-Tricks, and it’s a perfect fit for both demonstrations and using it in production sites. Almost without any modification, we can reuse the code from the previous example. We only need to add the proper directives and in the right places.
Wrapping up
Matching the front-end experience that WordPress form plugins provide can be done with relative ease for straightforward, no-fuss forms — and in a way that is reusable from project to project. We can even accomplish it in a way that allows us to switch the plugin without affecting the front end.
Sure, it takes time and effort to make a multi-page form, previews of the uploaded images, or other advanced features that we’d normally get baked right into a plugin, but the more unique the requirements we have to meet, the more it makes sense to use the submission endpoint as we don’t have to work against the given front-end implementation that tries to solve many problems, but never the particular one we want.
Using WordPress as a headless CMS to access the REST API of a form plugin to hit the submissions endpoints will surely become a more widely used practice. It’s something worth exploring and to keep in mind. In the future, I would not be surprised to see WordPress form plugins designed primarily to work in a headless context like this. I can imagine a plugin where front-end rendering is an add-on feature that’s not an integral part of its core. What consequences that would have, and if it could have commercial success, remains to be explored but is a fascinating space to watch evolve.
The Gatsby team shipped an update to its source plugin for WordPress, graduating it to a beta release. The new version brings a new set of features to Gatsby’s headless WordPress configuration, which brings together WPGraphQL and WPGatsby to power a Gatsby front-end that pulls in data from WordPress.
If you haven’t encountered these plugins before, that’s probably because they’re only available on GitHub rather than the WordPress Plugin Directory.
And if you’re wondering what the big deal is, then you’re in for a treat because this may very well be the most straightforward path to using React with WordPress. WPGraphQL turns WordPress into a GraphQL API server, providing an endpoint to access WordPress data. WPGatsby optimizes WordPress data to conform to Gatsby schema. Now, with the latest version of gatsby-source-wordpress@v4, not only is the GraphQL schema merged with Gatsby schema, but Gatsby Cloud is tossed into the mix.
That last bit is the magic. Since the plugin is able to cache data to Gatsby’s node cache, it introduces some pretty impressive features that make writing content and deploying changes so dang nice via Gatsby Cloud. I’ll just lift the feature list from the announcement:
Preview content as you write it with Gatsby Preview
Update or publish new content almost instantly with Incremental Builds, available only on Gatsby Cloud
Links and images within the HTML of content can be used with gatsby-image and gatsby-link. This fixes a common complaint about the original source plugin for WordPress.
Limit the number of nodes fetched during development, so you can rapidly make changes to your site while creating new pages and features
Only images that are referenced in published content are processed by Gatsby, so a large media library won’t slow down your build times
Any WPGraphQL extension automatically makes its data available to your Gatsby project. This means your site can leverage popular WordPress SEO, content modeling, translation, and ecommerce plugins through a single Gatsby source plugin.
Live previews are super nice. But hey, check out the introduction of incremental builds. That means no more complete site rebuilds when writing content. Instead the only things that get pushed are the updated files. And that means super fast builds with fewer bugs.
Oh, and hey, if you’re interested in putting a React site together with WordPress as the CMS, Ganesh Dahal just started a two-part series today here on CSS-Tricks that provides a step-by-step walkthrough.
Storyblok is a headless but component-based CMS with a built-in live-preview. You can use it for building fast and reliable websites and power native apps with your favorite technology.
A headless content management system is a back-end only content management system (CMS) built from the ground up as a content repository that makes content accessible via a RESTful or GraphQL API for display on any device.
At Storyblok, you can select from a large amount of already existing tutorials that will get you started. Jump into the free plan and start modeling your content. Each space can have an unlimited amount of content types and components that you can define.
Storyblok also enables you with more built-in tools like the free image optimization service to speed up your website performance and overall progress.
As a web development agency, you can also sign-up for the partner program. This will allow you and your team to have an unlimited amount of free staff members. You will be able to create as many development spaces for pitches and proof of concepts as you like. The best thing, you will not only get those users and spaces for free – you will also earn a monthly revenue share for every subscription your customers do at Storyblok.
Using Vue, Nuxt, axios, and Netlify, it’s possible to get both the performance and continuous integration benefits of Jamstack with the powerful publishing and editing features of a CMS. It’s really amazing what pairing different stacks can do these days!
Being a WordPress junkie myself, I learned from a lot from Sarah about setting up a progressive web app and working with a component-driven architecture. She equipped me with several resources, all of which are linked up in the article. There’s even a complete video where Sarah walks through the same steps we followed to set things up for this app.
In other words, it’s worth the estimated 18 minutes it takes to read the article. I hope you walk away with as much as I did getting to work on it.