Skip to content

This Blog Is Now Powered By WordPress

August 14, 2020

I decided as part of my recent site re-vamps to just get my blog posts from the WordPress Rest API. Integration went smoother than I expected and I was quickly up and running with my post content being served headless from WordPress as opposed to the Vue templates I was managing previously. With the new Gutenberg editor I should still be able to control my post styles fairly heavily once I start building out custom blocks for my posts.

The beautiful part here is that my site is still Vue.js to the core. The only thing I’ve changed is where my blog post content is coming from. Lets take a look at some code and how I made this conversion…

First thing I ran into after attempting to Fetch from my REST API due to the way I intended to use this API was CORS issues. By default, both my server and WordPress will not allow for cross origin requests. I fixed this in my WordPress theme’s functions.php


function handle_preflight() {	
	$origin = get_http_origin();
 	if ( $origin == 'http://origin.one' ||	$origin == 'https://origin.two') {
		// You can set more specific domains if you need
    		header("Access-Control-Allow-Origin: " . $origin);
		header("Access-Control-Allow-Methods: GET, OPTIONS");
		header("Access-Control-Allow-Credentials: true");
		header("Access-Control-Allow-Headers: Authorization");

		if ( 'OPTIONS' == $_SERVER['REQUEST_METHOD'] ) {
			status_header(200);
			exit();
		}
	}
}
add_action( 'init', 'handle_preflight' );

Also notice here that I set this API up to only allow for GET and OPTIONS requests. I do not have a need to accept anything else at this endpoint, OPTIONS is only included because it is used for the request pre-flight check. I then started tackling getting my posts into my Vue store so I can use them throughout the site.


export default new Vuex.Store({
  state: {
    posts: []
  },
  getters: {
    getPosts: state => {
      return state.posts
    }
  },
  mutations: {
    updatePosts: (state, payload) => {
      state.posts = payload
    }
  },
  actions: {
    getPosts ({ state, commit }) {
      if (state.posts.length) return
      try {
        let posts = fetchPosts()
        commit('updatePosts', posts)
      } catch (err) {
        console.log(err)
      }
    }
  }
})

This was easy enough, just taking the time to set up the getPosts() action, one thing to note here is that we want to check that store to see if it already has a value for posts if it does, it will escape the action and return. I don’t need to make extra API calls, I already have the data. Other thing to note here is that I setup a Getter, but I’m not using it at this time. Its mostly just planning for the need in the future, should it come up.

I set up the API call to happen in the main App.vue, because I intend to reuse the post data throughout the application, I should retrieve it at the top level. I just added getPosts to my map actions, and call it inside the created() lifecycle hook.

export default {
  name: 'mainApp',
  methods: {
    ...mapActions([
      'getPosts'
    ]),
  },
  created () {
    this.getPosts()
  }
}

I then turned to deal with the blog page. This is a page that has a number of post previews displayed on it, along with a list of every blog post. The homepage has a similar treatment so we can reuse this there. I had already been iterating through a flat file that had an index and meta data for all of my posts, so the template was not going to change much, only the data source. I used mapState to get posts from the store. From there it was simply a matter of changing all of the selectors to match the new posts data.

<template>
  <div class="blog">
    <section class="posts-list">
      <div class="container">

        <ul class="list grid grid--2">
          <li v-for="(post, index) in posts"
            :key="index"
          >
            <router-link :to="`/blog/${getPostYear(post)}/${post.slug}`">
              <span class="title">{{ post.title.rendered }}</span> 
                - <span class="date">{{ post.x_date }}</span>
            </router-link>
          </li>
        </ul><!--/.list-->

      </div><!--/.container -->
    </section><!--/.posts-list -->
  </div><!--/.blog-->
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'blog',
  computed: {
    ...mapState([
      'posts'
    ])
  },
  methods: {
    getPostYear (post) {
      return `${post.x_date.slice(-4)}`
    }
  }
}
</script>

Finally, I had to make a new posts page for each blog post. I’ll only need one template for this. I grabbed a generic template that I had availible and began changing things. I quickly realized that I had a problem with routing, as I wanted to make sure I was able to generate the routes dynamically. Luckily Vue can help us with that, having Dynamic Routing available out of the box. I was previously generating my routes dynamically from the Index file I had created in what I now realize was a terrible way to handle the problem. So I added this route to my router.js.

{
  path: '/blog/:year/:slug',
  name: 'post',
  props: true,
  components: { main: () => import(/* webpackChunkName: "blog" */ './views/Post.vue') }
}

With that problem solved I turned back to the post view itself. I quickly got the page to work, but on a hard refresh (which would also be a user landing directly on a post) no data was populating the template, and I was not getting any errors. After some digging I landed on this piece of the Dynamic Route Documentation…

“One thing to note when using routes with params is that when the user navigates from /user/foo to /user/bar, the same component instance will be reused. Since both routes render the same component, this is more efficient than destroying the old instance and then creating a new one. However, this also means that the lifecycle hooks of the component will not be called.”

I was indeed relying on the mounted() lifecycle hook to help with my render. So I added a watcher to look for changes to posts, the idea being that when the data was present, I could then iterate on it and extract the correct post to display for my given route.

<template>
  <div :class="`post post--${route.slug}`">
    <section>
      <div class="container">
        <div class="post-info">
          <span class="post-date">{{ currentPost.x_date }}</span><!--/.post-date-->
        </div><!--/.post-info -->
        <div v-html="currentPost.content.rendered"></div>
      </div><!--/.container -->
    </section>
  </div><!--/.about-->
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'post',
  computed: {
    ...mapState([
      'posts'
    ])
  },
  watch: {
    posts (value) {
      this.getCurrentPost(value)
    }
  },
  data: function () {
    return {
      currentPost: {
        title: {
          rendered: 'loading...'
        },
        content: {
          rendered: 'loading...'
        },
        x_date: 'loading...'
      },
      route: this.$route.params
    }
  },
  methods: {
    getPostYear (post) {
      return `${post.x_date.slice(-4)}`
    },
    getCurrentPost (postsArray) {
      const findPost = postsArray.filter(item => item.slug === this.route.slug)
      this.currentPost = findPost[0]
    }
  },
  mounted () {
    this.getCurrentPost(this.posts)
  }
}
</script>

Okay, so whats going on here… When this view loads, several things happen — the value of currentPost is immediately set to a loading state. Then when the component mounts it attempts to getCurrentPost from (posts), it may or may not succeed at this task, depending on how the user landed on the page. At the same time I am also setting at watcher, that is watching my posts from the store, if the value of posts changes the watcher will also attempt to getCurrentPost(posts). Once I have the current post (matched by the slug, which I have available from the router), it will display the correct post data on page.

This all works, and as you can see the posts are displayed on the page correctly. However, for the time being I’ve settled on living with an error. TypeError: _vm.currentPost is undefined

This error will occur if the user lands directly on the post, or if you were to hard refresh this page right now. The page will still load correctly, but we have the error… So whats going on? I believe that when the App mounts on the post page it does not have the context of this, but so far I’m not able to find the correct way to bind my functions so that this can remain in scope at that early stage of the load. However, the mounted() lifecycle hook appears to be necessary for users who navigate to this page from a link within my domain, or already had data cached. I will certainly update this post once I’ve solved the issue. But for now I need to be transparent about the “good enough” solution I’ve put in place.