Decoupled and headless content management systems (CMS) keep gaining more and more traction. The concept is that we do not need to be bound to the restrictions of a full-stack CMS. Instead, we can choose to use it for only one thing—managing content.
The largest player in the CMS space is WordPress. Even today, it stands at about a 60% market share and is the solution asked for by name by most clients. Most people in the content or marketing department of a company have at least a moderate amount of exposure and familiarity to managing a site through WordPress, which helps them get brought up to speed when the new website is ready. While WordPress comes with the benefit of user-familiarity and ease of customization, it also comes with some really big deficits. It is infamous for needing a lot of hand-holding for speed issues, requiring frequent plugin updates, regular security patches, and an outdated templating system. Decoupling your website from your CMS means your solution can be composed of whatever technology you’d like and proactively prepares your content to be consumed by another service or any piece of technology with an internet connection.
Today I’m going to show you how I use WordPress to power my Nuxt applications. Nuxt is an application framework powered by Node.js and Vue.js that allows you to build sophisticated, cutting-edge, and universal applications, statically generated web applications, or single-page applications (SPAs). For a full rundown on Vue, check out our recent article on Everything You Need to Know About Vue JS.
Recommended Plugins
Below are a few plugins I use to enhance the WordPress CMS and its corresponding REST API.
- Advanced Custom Fields Pro (ACF Pro): In my opinion, this is a must-have plugin for any WordPress project. It’s a plugin made for developers that allows you to create intuitive interfaces for the end-users while modularizing your fields into groups and exposing them to their relevant portions of the admin.
- ACF to REST API: A simple plugin that automatically feeds ACF meta information into the post or page response of the REST API. It’s a simple install and enable.
- Gravity Forms: There are many form builder solutions out there, but I enjoy this solution quite a bit. In combination with its official REST API plugin it delivers everything you would expect along with great documentation.
- Gravity Forms REST API: The official REST API that allows you to pull field data, push form submissions, and everything else you need for forms in a decoupled solution. There’s no link to provide because you’ll first need a PRO license. From there, you can download and install it for free.
- WP REST API: A simple plugin that allows you to supply filters for what data you would like to come back in your REST calls to reduce your JSON payload size. Simple and effective.
- WP REST API Menus: A simple plugin that extends the WP REST API to allow for you to pull your navigation menus.
- Yoast SEO: The de facto standard for managing your site’s SEO.
- WP REST API Yoast SEO: A simple plugin that automatically feeds Yoast SEO data into the post or page response of the REST API. It’s a simple install and enable.
Architect Your Vuex Store
There are plenty of articles out there that go into depth on what the Vuex store is in addition to developer best practices. However, for the extent of this article, I’m going to proceed as if you have a general familiarity with it already. If you are unfamiliar, visit the Vuex website to brush up on the subject.
state: {
user: {
fontSize: 16
},
common: {
states: new stateCollection.UsaStates().states
},
modal: {
default: {}
}
}
The example above shows a shallow example of what I generally begin with. A “user” object whose entire purpose is to store user decisions in the browser somewhere via a package called Vuex Persisted State (in the form of cookies, session storage, or local storage). This is a good place to store result filters, site-wide font scaling preferences, and other elements you want your site to remember the next time your visitor comes back to your site. Obviously, do not store any personally identifiable information. A “common” object stores helpful information you want globally available in your app. Finally, a “modal” object stores what information you would like to display in your site’s modal.
From there, you can begin building out the rest of your Vuex store to support the data architecture of the rest of your website.
Create An Empty Cache Object
let cache = {}
const store = () => {
return new Vuex.Store({
state: {},
plugins: [],
mutations: {},
getters: {},
actions: {}
})
}
Outside of your Vuex store, create an empty “cache” object. This is where you can store REST API responses for future use. If you don’t need real-time data every time the page is pulled up by a user, you can store it here and serve the response from memory instead of querying the REST API every time.
Vuex Mutations
Most of the time, you’ll only ever use this one mutation.
set (state, payload) {
Object.entries(payload).forEach(function ([key, val]) {
_.set(state, key, val)
})
}
Ain’t she a beaut? This allows you to write data to your Vuex store. In addition, “_.set(…)” is a helper function from a library called Lodash I use occasionally. But, if you’re a JavaScript elitist and need to write everything in vanilla JS then by all means, rewrite that to your heart’s desire.
Usage from inside a Vuex action
commit('set', { 'key': value })
commit('set', { ['nested.key']: value })
Vuex Actions
This is where you’ll handle reaching out to WordPress via the REST API. Once you have the response from the REST call, you can decide if this is a response you would like to cache and either store it in cache before returning the Replace [API_URL] with the secure path to your WordPress REST endpoint.
getSiteInfo: async function ({ commit }) {
if (!cache.site) {
let siteInfo = await this.$axios.$get(`${[API_URL]}/acf/v3/options/site-information`)
cache.site = siteInfo.acf
}
commit('set', { 'site': cache.site })
}
The above example fetches site information from the WordPress ACF REST API endpoint if it doesn’t already exist in cache. If already exists in cache the action merely returns what’s already present. The first user visiting a page that uses this information should be the only time a query is made to the API until the cache is invalidated.
Vuex Getters
Truth be told, I don’t use getters very often. Instead, I built a helper function to use inside components to do a bi-directional sync of Vuex store data. This part is up to you, but here’s my tool and how to use it:
components/tools.js
import _ from 'lodash'
const tools = {
bind: function (key) {
return {
get() {
return _.get(this.$store.state, key)
},
set(val) {
this.$store.commit('set', {[key]: val})
}
}
}
}
export default tools
New Vue component: Import tools at the top
import tools from '~/components/tools'
New Vue component: Create a computed
computed: {
componentVar: tools.bind('vuexVar')
}
Alternatively, you can use Vuex mapState for simple one-to-one binding. But, this gives you the added benefit of accessing an object a few levels deep and allows you to give it a different name inside your component.
Nuxt Server Init
This is the function that is called the first time your Node process runs. This is where you can dispatch all the actions you built that pull data from the REST API and populate your cache and store.
store/index.js
async nuxtServerInit ({ commit, dispatch }) {
await dispatch('getSiteInfo')
await dispatch('getNetworkInfo')
await dispatch('getNavigation')
await dispatch('getForms')
await dispatch('getCustomPostType')
}
All these actions work similar to the example provided above in the Vuex Actions section, making calls to the WordPress REST API and populating the responses into cache. The first time your app starts up, this method is invoked and populates your Vuex store with most of the information it will need.
This speeds up your page by reducing the amount of REST requests the server needs to make to render the page to the user. You can even orchestrate your pages to pull and cache individual page information so the first user that requests a particular page is the only person who has to wait for the request/response of the REST call. Each subsequent request to that page will be served the same response from cache without ever needing to make a call to WordPress.
Cache Invalidation
The last part of this process is to purge a portion of the cache object when data is updated in WordPress. You can accomplish this by tapping into the following WordPress hooks:
- wp_update_nav_menu: This hook fires when the navigation is updated under Appearance > Menus.
- save_post: This hook fires whenever a post or page is updated.
- acf/save_post: This hook can be used to track when an Options page is saved.
From within the WordPress hook, you’ll want to CURL a custom path on your site whose purpose in life is to purge portions of your cache. The great news is that Nuxt makes this incredibly easy through middleware.
middleware/cache.js
export default async function ({store, route}) {
if (route.path === '/path/to/cache/clear/url') {
store.commit('clearCache')
}
}
This is a simple example of how to commit a mutation in your Vuex store to clear the cache when a resource is requested. At this point, I highly encourage you to expand upon this example with security in mind. This is a public route that could be abused if you don’t lock it down in some way.
Conclusion
WordPress is now ready to syndicate your information properly inside its REST API and clear your application’s cache when posts, pages, option panels, and navigation menus are updated. Your Nuxt application either pulls in fresh information from WordPress into your Vuex store or serves it from cache. Every single Vue component in your site can now push and pull data from your Vuex store and count on it being up to date and quicker than ever.