Skip to content

Using Nuxt to Create a Statically Generated Site From WordPress Like Gatsby

I’ve recently been introduced to nuxt.js which is a delightful framework for building apps in vue. It gives you the options to create a server-side rendered application off a node server, create a single page app, or generate a static site. It generates routes and makes the server for you, but you need to follow its conventions (which I’m happy to oblige!)

I love Gatsby, so I thought at first this would be the perfect marriage of a gatsby-ish site written in Vue. There are some issues…

Here is a writeup about the current state of statically generating with nuxt. This article will be in three parts.

  1. The set up (how to get nuxt going)
  2. The static generation (how to generate wordpress pages)
  3. The problem (why it isn’t like gatsby)
  4. The solution (how to make it like gatsby)
  5. The future real solution (nuxt might have their own solution)

The set up

If you want to see the complete repo of this example: https://github.com/JimSchofield/nuxt-wordpress-static-generator

Getting a baseline project in nuxt up and running is dead simple. Make sure you have npx installed, and run npx create-nuxt-app vatsby (vatsby = vue gatsby. But you can change that to be whatever folder name you want.)

It will then ask you a bunch of questions about what you want to use. I just hit enter to everything and this is the result:

After all the generation is done, you have instructions to build your site!
After all the generation is done, you have instructions to build your site!

Okay, so go ahead and cd into your project and do npm run dev.

It sets up a server at http://localhost:3000/ , and voila! You have a site up and running.

What if you wanted to build a second page? Simply put a new page (like Page2.vue) inside the pages directory.

<template>
  <h1>Page 2</h1>
</template>

<script>
export default {
  name: 'PageTwo',
}
</script>

To link to this newly created page, we use the <nuxt-link> component. Here I’m going to add a link to index.vue:

<nuxt-link to="/page2">Page 2</nuxt-link>

If you save, the server should recognize changes, rebuild, and routing is all taken care of for you.

So how do we generate pages from a WordPress site?

The generation

If you run npm run generate, you will notice that a live server doesn’t spin up. You do get a new folder called dist.

That’s what a static site generator does, it runs the framework js and renders out each individual page as separate entrypoints that all load up the same single page app after you go to them.

If you cd into the dist folder and use a simple server like http-server (or just open the file in your browser) you can see your site again, but there is no node server running the background. If you load up index.html or page2.html, you should see the same thing.

So what’s the benefit? Well, like server side rendering, a search engine will be able to scan your site and know what’s on there without having to load javascript and have javascript paint your site out.

Javascript does come in later and “hydrate” your site, so it ends up being a single page app. But this way we have the benefits of server side rendering without the server! If you think about it… 99% of your average blog and marketing pages don’t change, so just generate them once, right?

Okay, so how do we get WordPress pages?

The secret lies in nuxt.config.js. There is a configuration that allows us to instruct nuxt to render out certain routes and views. Here is what we’re going to add to the end of the config object:

import axios from 'axios'

module.exports = {
    // Other configs....
    generate: {
        routes: async function()  {
            const response = await axios.get('http://jschof.com/wp-json/wp/v2/posts')
            const routes = response.data.map(post => {
                return {
                    route: '/post/' + post.id,
                    payload: post
                }
            })
            return routes
        }          
    }
}

When you run npm run gen nuxt will run this routes function. It will go out to my website wp-json endpoint (that every wordpress site has) and get the first page of posts. For every post, we add an object that defines the route to use, and gives that route the post data. The array we make we return, and nuxt does the rest.

So how does nuxt know which vue file to use to render? We need to make it. Here is a template under /post/_id.vue. The underscore _ means that this is a dynamic route and changes based off of which id gets passed to it. Here’s the vue file I made.

<template>
  <div>
    <h1>Post id: {{ post.id }} </h1>
    <div v-html="post.content.rendered"></div>
  </div>
</template>

<script>
export default {
  name: 'PostPage',
  async asyncData({ params, error, payload}) {
    if (payload) {
      return { post: payload }
    }
  }
}
</script>

The asyncData function allows us to accept the post data from the nuxt config we were just talking about. It then lets us access post in the template!

If you did these things and run npm run generate, you’ll see a bunch of pages being generated…

A list of generated post pages

The Problem with this method for a static site

Okay, so this is great! What’s the issue?

Well, let’s create a post index page, and then we’ll see where this isn’t quite like gatsby.

Let’s create a post index page template in /post/index.vue.

<template>
  <div>
    <h1>Post index:</h1>
    <ul>
      <li v-for="post in posts">
        <nuxt-link :to="'/post/' + post.id">{{ post.id }}</nuxt-link>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'PostIndex',
  async asyncData({ params, error, payload}) {
    if (payload) {
      return { posts: payload }
    }
  }
}
</script>

And lets add a little bit to to our generate function:

routes.push({
    route: '/post/',
    payload: response.data
})

Now when we generate, we should get a nice index page for our posts at /post. Here I went into /dist and ran http-server (which you can get from npm. Then I went to localhost:3000/post/

Post Index listing

Okay, so what’s the problem? When we click on one of those posts we go to the right vue view, but there’s nothing there!

But wait, if you reload the page, we get the post showing….

So what’s going on?

When you go to any of the pages directly, you are seeing immediately the statically generated markup. Once you’re there, nuxt takes over and loads up vue, and once that happens you’re actually looking at a single page app. When you navigate to another page, though, there is no data. The data was available when you generated your pages, but it’s not there anymore! But if you reload the page, you then basically go back to the files get the statically generated content.

Gatsby handles these things out of the box. But here, we need to solve it ourselves.

The Solution

Let’s get direction from how gatsby handles this static site issue. Gatsby saves any data it gets from doing a fetch request and actually stores that data as json along with the statically generated page when it’s generating all the pages. Then, gatsby tells the page to actually go out and grab that data from the json if the data doesn’t exist (i.e. the single page app is engaged.)

So the page is still basically static… its data is just separated into a json and loaded when it’s needed.

Let’s do this.

Caveat: This is more of a proof of concept. If you were to engineer this for a project, you would need to do a lot more work to be careful about performance, maybe split your json by page, etc., etc.

First, we’re going to be lazy and just store the entire response in one json file. We’re going to use a node function to write a file out to the dist folder.

// at the top of the file nuxt.config.js
import fs from 'fs'

// right before returning routes
fs.writeFileSync('dist/data.json', JSON.stringify(response.data))

If you run generate now, you’ll see a nice data.json file in your dist folder now.

Let us have individual blog posts actually fetch this json and re-hydrate the page data if it needs to. The key is to do it in asyncData when there is not payload being passed.

async asyncData({ params, error, payload}) {
  if (payload) {
    return { post: payload }
  } else {
    const res = await fetch('../../data.json')
    const data = await res.json()
    return {
      post: data.find(post => post.id === parseInt(params.id))
    }
  }
}

So, if the app is loaded, it will try to get the payload when you get to that post. The payload wont be there because payload is strictly for static generation. SO, it fetches our stored json and picks out the correct post by id.

Yay… I think. ?

Homework: You can test your understanding by trying to have this happen also for the post index page. Since that relies on payload data, you would need to hook up data.json to that vue component too. Give it a try!

The Future Real Solution

Right now, it really feels like we shouldn’t have to wire this up, and it leaves you feeling a little empty inside.

Hopefully, though, it will be built into Nuxt. Some Nuxt people are saying soon: https://twitter.com/nuxt_js/status/1177586547664793601

Until then, hopefully this walk through illuminates some static site generation, nuxt, and the issues we still have with this paradigm.

Update: There are some plugins and helpers to get this going for you. See one example here: https://github.com/stursby/nuxt-static/

Leave a Reply

Your email address will not be published. Required fields are marked *