Creating a WordPress Theme using the REST API and Vue.js

db-creatingawpthemepoweredbyrestapi-icons

After attending the Day of REST conference a couple of weeks ago I was inspired to dive in to the WordPress REST API and test it out. On the day Jack Lenox from Automattic gave a talk entitled “There and Back Again: A Developer’s Tale” in which he described his experience building a WordPress theme using the REST API and React. His insights were interesting and he clearly spelled out some of the challenges of building a WordPress theme as a single page application:

  • Search Engine Optimisation (SEO)
  • Client/server code partitioning
  • Browser history
  • Analytics
  • Speed of initial load

Some of these issues are easier to overcome than others. For example, browser history can be implemented in JS using the History API and Google Analytics has support for Single Page Application Tracking.

Client/server code partitioning on the other hand is a bit more complex as you don’t want to recreate your templates on the backend and frontend in different formats. One way to get round this is to use Nodejs to render templates on the server and use the same template on the front end, but this has the downside of needing to run Nodejs alongside PHP.

SEO is understandably a major problem for most people and while most big search engines crawl JavaScript rendered sites to a certain extent, the results are not great and most social networks will not crawl content when you share it.

Performance is important and one of the other big issues with single page applications is the speed of initial load. Jack went so far as to suggest automatically generating PHP template files from rendered JavaScript templates as a way to speed things up (i.e. let the server render the initial page load and let JavaScript take over once loaded) which made most people in the room cringe (and rightly so).

Plan of Action

After considering these issues and potential solutions I decided I would see if I could build a simple skeleton WordPress theme using the REST API and Vue.js. Vue is similar to React in that they both provide reactive and composable View components but, as you might expect, the implementation differs in many ways.

All of the code from this article and the entire working theme can be found over on the GitHub repo if you want to follow along and hack up your own theme.

So how am I going to deal with the client/server code partitioning, SEO, and performance issues? Basically the plan is to create a very simple base WordPress theme which will output the no-frills content for the page being viewed then bootstrap Vue on top of this to power the site after the initial page load. This will allow search engines to crawl the content for the given page while real users won’t see the content rendered from the server and will only see the Vue application. We’ll then use vue-router to handle navigating the site (including history and back button) and vue-resource to handle fetching content from the WordPress REST API.

The WordPress Part

The WordPress part of the theme will only include three files:

  • style.css – Includes theme meta information used by WordPress and global styles.
  • functions.php – Used to enqueue our scripts and styles and output information we can’t retrieve from the REST API.
  • index.php – Outputs the very basic HTML content of the page to be crawled by search engines.

Here is the code for index.php:

<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo( 'charset' ); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="profile" href="http://gmpg.org/xfn/11">
    <link rel="pingback" href="<?php bloginfo( 'pingback_url' ); ?>">
    <?php wp_head(); ?>
</head>
<body>

    <div id="content">
        <?php
        if ( have_posts() ) :

            if ( is_home() && ! is_front_page() ) {
                echo '<h1>' . single_post_title( '', false ) . '</h1>';
            }

            while ( have_posts() ) : the_post();

                if ( is_singular() ) {
                    the_title( '<h1>', '</h1>' );
                } else {
                    the_title( '<h2><a href="' . esc_url( get_permalink() ) . '">', '</a></h2>' );
                }

                the_content();

            endwhile;

        endif;
        ?>
    </div>

    <div id="app"></div>

    <?php wp_footer(); ?>

</body>
</html>

As you can see, server rendered content goes in a #content div (which will be hidden) and used by search engines. The Vue application will then be bootstrapped into the #app div. That’s about it for the WordPress part of the theme.

The Vue Part

The Vue part of the theme can be found in the rest-theme folder. The Vue application is created in the src/main.js file. Here we create the main App component with a simple template:

template: '<theme-header></theme-header>' +
          '<div class="container"><router-view></router-view></div>' +
          '<theme-footer></theme-footer>',

The theme-header and theme-footer components are defined in their own files. The router-view component is used by the vue-router library to render whatever component is passed to it for the current page.

One issue I came across while building the theme was how to recreate the WordPress routes in Vue. For larger sites it probably makes sense to use Vue’s Async Components to only load routes when they are needed, but for a small site I decided to simply output all of the routes using wp_localize_script in functions.php:

function rest_theme_scripts() {
    //...
    wp_localize_script( 'rest-theme-vue', 'wp', array(
        'root'      => esc_url_raw( rest_url() ),
        'nonce'     => wp_create_nonce( 'wp_rest' ),
        'site_name' => get_bloginfo( 'name' ),
        'routes'    => rest_theme_routes(),
    ) );
}
add_action( 'wp_enqueue_scripts', 'rest_theme_scripts' );

function rest_theme_routes() {
    $routes = array();

    $query = new WP_Query( array(
        'post_type'      => 'any',
        'post_status'    => 'publish',
        'posts_per_page' => -1,
    ) );
    if ( $query->have_posts() ) {
        while ( $query->have_posts() ) {
            $query->the_post();
            $routes[] = array(
                'id'   => get_the_ID(),
                'type' => get_post_type(),
                'slug' => basename( get_permalink() ),
            );
        }
    }
    wp_reset_postdata();

    return $routes;
}

This will output the routes on the initial page load which we can then use to bootstrap our routes in our Vue router:

for (var key in wp.routes) {
    var route = wp.routes[key];
    router.on(route.slug, {
        component: Vue.component(capitalize(route.type)),
        postId: route.id
    });
}

I then created components that correspond to the route.type (posts and pages in this case). These individual components handle fetching the content from the REST API and rendering the output. For example:

<template>
    <div class="post">
        <h1 class="entry-title" v-if="isSingle">{{ post.title.rendered }}</h1>
        <h2 class="entry-title" v-else><a v-link="{ path: '/' + post.slug }">{{ post.title.rendered }}</a></h2>

        <div class="entry-content">
            {{{ post.content.rendered }}}
        </div>
    </div>
</template>

<script>
    export default {
        props: {
            post: {
                type: Object,
                default() {
                    return {
                        id: 0,
                        slug: '',
                        title: { rendered: '' },
                        content: { rendered: '' }
                    }
                }
            }
        },

        ready() {
            // If post hasn't been passed by prop
            if (!this.post.id) {
                this.getPost();
                this.isSingle = true;
            }
        },

        data() {
            isSingle: false
        },

        methods: {
            getPost() {
                this.$http.get(wp.root + 'wp/v2/posts/' + this.$route.postId).then(function(response) {
                    this.post = response.data;
                    this.$dispatch('page-title', this.post.title.rendered);
                }, function(response) {
                    // Error
                    console.log(response);
                });
            }
        }
    }
</script>

Note that I’m using the Vueify format here for single file components and ECMAScript 6 JavaScript.

One other issue that arose was updating the title element of the page when you change pages in the app. I decided to use Vue’s dispatch method to send an update to the parent component which would update the title:

// In the child component...
this.$dispatch('page-title', this.post.title.rendered);

// In the App component
methods: {
    updateTitle(pageTitle) {
        document.title = (pageTitle ? pageTitle + ' - ' : '') + wp.site_name;
    }
},

events: {
    'page-title': function(pageTitle) {
        this.updateTitle(pageTitle);
    }
}

What Next?

At this stage the theme is very simple and there are many things still to improve including:

  • Implementing WordPress menus instead of just outputting a list of pages
  • Caching so we don’t keep hitting the REST API if we already have the data
  • Implementing Google Analytics
  • Implement loading indicators and transitions
  • More advanced post/page components
  • Much more…

Anyway, I hope this article at least gives some insight and a starting point for creating your own WordPress themes powered by the REST API. I managed to overcome most of the hurdles I outlined at the start of the article, but that doesn’t mean there isn’t better ways to get around these issues. Remember the working theme and all code can be found over on the GitHub repo.

Have you tried building a WordPress theme with the REST API? What was your experience?

About the Author

Gilbert Pellegrom

Gilbert loves to build software. From jQuery scripts to WordPress plugins to full blown SaaS apps, Gilbert has been creating elegant software his whole career. Probably most famous for creating the Nivo Slider.

  • Jonathan

    This is a very nice example! Will surely try something like this in a near future.

    However, I am not sure of this : ”div (which will be hidden) and used by search engines.”

    Isn’t a violation of Google’s Webmaster Guidelines? – https://support.google.com/webmasters/answer/66353?hl=en

    • Unfortunately this is still a bit of a pain point. While most search engines can crawl Javascript generated content to a certain extent, support isn’t great. Google used to have an “AJAX crawling” API but it is now deprecated https://developers.google.com/webmasters/ajax-crawling/docs/learn-more.

      The webmaster guidelines state: “However, not all hidden text is considered deceptive. For example, if your site includes technologies that search engines have difficulty accessing, like JavaScript, images, or Flash files, using descriptive text for these items can improve the accessibility of your site”. I suppose at the moment we have to trust Google won’t penalise us for trying to make things accessible.

      • laetilaeti

        I am surprised nobody in this thread came up with the solution?
        This tag seems obsolete but it is still around! 🙂
        And in our case seems to be the most semantically appropriate solution…
        (Last but not least, that’s what Google recommands to use… sorry I read it some days ago but I cannot find it now)

        What would you think of that ?

  • joshuaiz

    Yep, you lost me at “div (which will be hidden)” – you will definitely get penalized by Google for this.

  • daronspence

    Really great example Gilbert! Been playing with Vue myself and was a little confused about dispatching so that helped clear things up!

  • Edouard Duplessis

    An alternative to hidding your traditionnal content “div (which will be hidden)” …. why you don’t replace it by the one generated by VUE http://vuejs.org/api/#replace

    Doing it this way it’s not a violation of Google Webaster Guidelines

  • Bonsak Schieldrop

    Great write-up. Thanks.

  • Orleans

    Great article! I would love to see a part 2. The problem I’m having is creating custom page templates. I would love to see the workflow of adding a route to a specific page, with nested route-viewer and their templates. (Example: Menu Page with Breakfast/Lunch/Dinner subroutes, all being styled differently)

  • Very great example .. Thanks Gilbert 🙂

  • Ant

    Vue 2.0 will support server side rendering, so you won’t have to write initial page in php. Just one vue template pre-rendered on the server, then sent to web browser and maintained on the browser side. If user decides to load more data – it will modify the prerendered template on client side since then 😉

  • markesesr

    where part 2 ?