Headless WordPress with GraphQL + Vue3

Not as spooky as it sounds!
Whenever I hear the term “headless” I flash back to watching the Disney animated story The Legend of Sleepy Hollow where the headless horseman terrorizes the movie’s fearful protagonist Ichabod Crane- it must have had a big impact on me. What we’re actually talking about today, however, is not nearly as spooky as that.
What we are going to look at today is how to author content for the web in WordPress and display that content on a site built with VueJS 3. If you are reading this right now then you are actually seeing a live demo of this concept.
Note: if you are hoping to implement this code in your own project you will probably need to be somewhat familiar with WordPress and how to set up and write code in a VueJS project that uses routing.
Separation of Concerns
I love WordPress as a CMS but I am also constantly drawn toward the modern JS frameworks because they are new and fun but also performant and can lead to engaging user experiences. Even Matt Mullenweg has said that JS is the future of the web, and I believe him.
I’ve found that a sweet spot for my current development style is the marriage of a rock-solid CMS with a fast and modular front-end JS framework. My go-to for each of these is WordPress and typically VueJS. I find editing in WordPress since the advent of the Gutenberg editor to be truly superior than many other off the shelf solutions more tailored to editing content for front-end frameworks. I like VueJS a lot better than React because it does what I need well and it just makes sense to me in terms of how it works. With a headless architecture I can have the best of both worlds.
New Vue, who dis?
VueJS had a big step forward with Vue 3 and their new (optional) Composition API which fills a lot of the gaps that were pain points in previous versions. Vue components can now be more flexible with less verbose boilerplate code than there had to be before, and authoring with the Composition API feels a lot more like vanilla JS as well. The best part of this new API though is that it is optional and interchangeable with their previous Options API many are already familiar with.
Data Consumption
Front-end JS frameworks are made to ingest data from APIs but the good news is that WordPress already has one! WordPress has had a powerful and well architected REST API for many years now. And while REST is great, we can do even better by layering GraphQL on top of the REST API in order to query our WordPress data quite easily. WordPress luckily has a very well built and supported plugin to help us do this called WP GraphQL. Not only does it set up a GraphQL layer on top of the built-in REST API but it also ships with a great in-browser IDE called WPGraphiQL which helps you build out and test your queries right inside the WordPress admin dashboard. It’s actually pretty amazing.

In VueJS we need a tool to plug these GraphQL queries into– but never fear, Vue has us covered with Vue Apollo. The only caveat here, which I ran into while working on this, is that if you are compiling your project with Vite you need to be running relatively new versions of Vue 3 and Vite in order to avoid some weird compiling errors. If you are using the Vue CLI as your compiler instead you should be fine on any version of Vue 3. Here are the package.json
versions that worked for me (including the Vue Apollo dependancies):
{
...
"dependencies": {
"@apollo/client": "^3.7.16",
"@vue/apollo-composable": "^4.0.0-beta.7",
"vue": "^3.3.4",
...
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"vite": "^4.3.9",
...
}
}
Luckily I was able to just manually change the version numbers and re-install the dependancies and everything worked with out any conflicts. It did take me almost a half a day to figure out why it wasn’t working in the first place :face-palm:.
Blogging in Style
My portfolio site was already built in Vue with most of the content being static, but I wanted to add a blog section and I wanted the editing experience to be enjoyable so I built out GraphQL queries for a post index view and a single post view.
Post Index View
The post index view is pretty simple. I added a new route in Vue and created a template for that view to show the list of posts from my WordPress site. To keep things simple for this post I will omit category filtering and pagination, but that is something I would like cover in a future posts.
To build out the post index view we need to query the data which you can see here in the <script>
tag of my PostIndexView.vue
file:
import { reactive } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const POSTS_QUERY = gql`
query Posts {
posts {
nodes {
id
databaseId
title
slug
}
}
}
`
export default {
name: 'All Posts',
setup () {
const {
result,
loading,
error
} = useQuery(
POSTS_QUERY,
variables
);
return {
result,
loading,
error
}
}
}
Then in the <template>
tag for the PostIndexView.vue
file we include some conditionals to manage state and then loop over the data we got back from that query:
<div v-if="error"><p>Something went wrong...</p></div>
<div v-if="loading"><p>Loading...</p></div>
<div v-else>
<article
v-for="post in result.posts.nodes"
:key="post.id"
>
<h2>
<a
:href="`/posts/${post.slug}`"
:title="`Read ${post.title}`"
>{{ post.title }}</a>
</h2>
</article>
</div>
Note here that the href value should match the route you plan to add for your single post view. In my case the route path I chose is '/posts/:slug'
.
Single Post View
For the single post view I wanted to have it routed programmatically and that proved to be super easy using Vue Router. All you need to do is add the props: true
option in the route entry that includes the :slug
in the path. Then that :slug
variable is what we use in our single post GraphQL query to programmatically retrieve the correct WordPress post based on the URL slug from the route. You can see that here in the <script>
tag of my SinglePostView.vue
file:
import { ref } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import gql from 'graphql-tag'
const SINGLE_POST_QUERY = gql`
query Post($postSlug: ID!) {
post(
id:$postSlug,
idType:SLUG
) {
id
databaseId
title
content
}
}
`
export default {
name: 'Single Post',
setup () {
const route = useRoute();
const variables = ref({
postSlug: route.params.slug
});
const {
result,
loading,
error
} = useQuery(
SINGLE_POST_QUERY,
variables
);
return {
result,
loading,
error
}
}
}
Better Living with Gutenberg
I tried this headless approach with WordPress before I had really switch over to Gutenberg when I still used Advanced Custom Fields to build out most of my page content, and dealing with the post content was a total pain. Now when authoring Gutenberg posts, the content we get back from our GraphQL queries is just regular old HTML.
The only work you have to do is add some additional CSS (which I am keeping in it’s own separate stylesheet in my Vue site) in order to accommodate the layout of some of the “fancy” Gutenberg blocks that could be used on the page like the Cover block or the Gallery block. All in all it is pretty simple, and you might even be able to port over some existing styles from your current WordPress site if you already have some.
Going Forward
There are a few other topics going forward I would like to cover in this series:
- Post index pagination
- Post index category filtering
- Server side rendering for SEO purposes
Keep an eye out for those topics in the coming weeks!