written by Andre Liem
06/29/2017

Build a Static Site with Nuxt (Part 3)

this is a re-post from my personal site andreliem.ca


Welcome to the first official post for my newly revamped personal site running off the awesome Nuxt framework [https://nuxtjs.org].
If you're not familiar with Nuxt, here is a quick summary straight from the official site.
Nuxt.js is summarized:

Nuxt.js is a framework for creating Universal Vue.js Applications.
Its main scope is UI rendering while abstracting away the client/server distribution.
Our goal is to create a framework flexible enough so that you can use it as a main project base or in addition to your current project based on Node.js.

In this post I'll review the following:

Introduction

I decided that it was time to move away from WordPress and adopt static blogging when [https://vuejs.org]
came out. Oddly enough I made this decision around the time that Nuxt was formalizing as a framework
for building Universal apps, but I never really gave the docs a read till recently.

I was looking at using Vuejs directly to build my Vue news site [http://vuejsradar.com] and building
my own layers, investigating SSR. All of this was posted in my first two posts on the site. Fortunately, I read into
Nuxt in more detail and realized it could handle a lot of the features I wanted straight out of the box.

While there has been static blogging tools like Jekyll for a while, I really like the idea of using Nuxt because it's already
part of a the Javascript / VueJS eco system which I understand already. Using gems and Ruby is fairly foreign and
is not something I'm interested in adding to my already full plate of "Full Stack" skills.

To meet the most basic needs of my personal site, I really only wanted 3 things to get going:

  • Standalone files to store my site blog posts (ideally in Markdown and/or HTML)
  • Meta data stored in an array (vuex)
  • slug / pretty urls mapped to content

Fortunately, doing all of this was pretty easy. The remainder of this post is going to review how I setup my personal
site. Eventually I will be updating the the repository I setup [https://github.com/andreliem/vuecms] to reflect the new
direction of basing everythig off of Nuxt.

For now you can clone my actual personal site if you like to try it out. [https://github.com/andreliem/andreliem.ca].
There isn't anything private about my site at the moment so I wanted to share it with developers early on. Just note that
I might make it private down the road when all my efforts are focused on this repo I setup here: [https://github.com/andreliem/vuecms]

Getting Setup

Getting setup with Nuxt was as painless as getting setup with Vue JS using the CLI.

vue init nuxt/starter <project-name>

cd <project-name>
$ npm install
npm run dev

From here you can author components as you would normally to represent content on
your site. With this basic setup you could blog by creating one Vue file per post.

But what I wanted was a more streamlined approach which only relies on one single Vue file to render to posts
and pulls in data from a Vuex store.

Slug Style Blog Posts

WordPress popularized the pretty url format (slugs) for blog posts and is pretty important to have for SEO purposes.
Doing this with Nuxt involves the following:

mkdir pages/_slug 
touch pages/_slug/index.vue

That's more or less it! You create a directory with an _ prefix and then Nuxt automagically
maps the routes for you.

e.g. - /i-am-a-slug can be accessed in index.vue as the slug property of params.

this.$route.params.slug

Vuex for representing meta data

Nuxt provides great support for Vuex, but there's a slight difference in how you need to define your state.

import Vuex from 'vuex'

const makeStore = () => {
  return new Vuex.Store({
    state: {
      posts: [],
      post: {}
    }
    ...
  })
}
export default makeStore

Notice how we are exporting a function which creates a Vuex.Store? It's a minor change but is needed for Vuex with Nuxt. In this above example, I have defined a basic array of JSON data to represent each post.

Loading the Post from Vuex

To pull post data from the store, I use the special Nuxt fetch method.

The fetch method is used to fill the store before rendering the page, it's like the data method except it doesn't set the component data.
[https://nuxtjs.org/api/pages-fetch/#the-fetch-method]

Nuxt is doing a lot of work for us here. We have access to the store, params and don't forget that the _slug directory will ensure
we have the slug value in the params.

Thanks to DavidRoyer_ for sharing his repo he's built for his Nuxt site, helped me figure out that the fetch method is what I needed.

In this particular code snippet below, I am running two dispatches to load up the data we need. If the site was running off
a traditional RESTful API I would probably just have 1 call here, but I'm currently storing it all locally and setting the posts
with getPosts. I intend on making
this better over time.

fetch ({store, params}) {
  store.dispatch('getPosts')
  store.dispatch('getPostWithSlug', params.slug)
}

Mark Down Loading + Parsing

I decided that Markdown would be my goto format for blog posts. Some day I might try to support others, like
straight up HTML+JS Inline, but for now Markdown is a great solution that is pretty standard for static blogging.

To accomplish this, two packages are required:

npm install marked --save
npm install vue-markdown --save

marked as it name suggests handles mark down files. This library will turn marked down content into HTML which
you can output easily using the v-html directive.

vue-markdown is a file loader that you'll need to include the markdown file dynamically.

After releasing this, I realized that nuxt has support for Markdown as a module. [https://github.com/nuxt-community/modules/tree/master/modules/markdownit].
In the future I will probably look at using this instead.

*update - I have now moved to using this module and it works much better.

Code Highlighting

Something that is really important for any developer blogging is having <pre> code with some syntax highlighting.
A great library for this is highlight.js.

npm install highlight.js --save

To hook this in to the markdown content, you need to add this before you parse the content.

Marked.setOptions({
  highlight: function (code) {
    return HighlightJs.highlightAuto(code).value
  }
})

Unfortunately, this does not seem to work 100%. I am still trying to figure out why but highlighting
is not working all the time. Moving to the markdownit module might solve this problem.

Commenting

Lastly, most personal sites need commenting so I decided to give the vue-disqus module a shot.

npm install vue-disqus --save

Configuring the module is pretty straightforward except for 1 issue I had with it
not re-rendering when the route/url changes. It's probably best if I put this all together in one code sample below.

_slug/index.vue

<template>
  <div class="post">
    <div v-html="postContent"></div>
    <disqus ref="disqus" v-bind:shortname="disqusShortname" :identifier="disqusId"></disqus>
  </div>
</template>
<script type="text/babel">
  import Marked from 'marked'
  import Disqus from 'vue-disqus/VueDisqus.vue'
  import HighlightJs from 'highlight.js'

  export default {
    layout: 'slug',
    components: {
      Disqus
    },
    head () {
      let post = this.post
      return {
        title: 'Andre Liem',
        meta: [
          {
            hid: post.meta.id,
            name: post.meta.name,
            content: post.meta.content
          }
        ]
      }
    },
    fetch ({store, params}) {
      store.dispatch('getPosts')
      store.dispatch('getPostWithSlug', params.slug)
    },
    computed: {
      post () {
        return this.$store.state.post
      },
      postContent () {
        let post = this.$store.state.post
        Marked.setOptions({
          highlight: function (code) {
            return HighlightJs.highlightAuto(code).value
          }
        })
        return Marked(require('../../content/posts/${post.id}.md'))
      },
      disqusShortname () {
        return 'andreliem-1'
      },
      disqusId () { // env used to avoid re-use from dev to production
        return '${process.env.NODE_ENV}-${this.disqusShortname}-${this.post.id}'
      }
    },
    watch: {
      '$route.params.slug' (curr, old) {
        // disqus does not properly reload just based off the
        // disqusId computed property - we need to manually change it
        // when we know it should update
        this.$refs.disqus.init()
      }
    }
  }
</script>

This file does the following:

  1. Find the post given the slug in the URL using a dispatch call
  2. Integrate code syntax highlighting with highlight.js
  3. Load a local markdown MD file using the id of the post as a unique file identifier.
  4. Load a unique Disqus commenting form for each post.

That's it! With this single file and layout which puts my face at the top you have a basic blog layout.

From here the process of blogging involves these steps:

  1. Create new entry in the Vuex state to represent the post.
  2. Create the markdown file and type away.

Deploying + Going Live

Once you are ready to go live, you can either deploy as a Server Rendered site (SSR) or Static. I'm interested
in deploying a static site, so the command to run is

npm run generate

This crates a dist folder that you can upload to your hosting service. I won't go over the tools you can use here
as there are some pretty neat solutions for hosting static sites... perhaps in another post. For now, all I'm doing
is hacking it through a github commit process. I'll look at improving that in the future but for now it serves my
needs.

The one thing to keep in mind is that for static sites, you need to define the URls that you wish to have
available in the nuxt.config.js file.

generate: {
  routes: [
    '/',
    '/getting-started-nuxt-markdown',
    '/my-top-10-favourites-of-being-a-freelancer-software-developer',
    '/setting-up-php-xdebug-with-laravel-vagrant-and-phpstorm'
  ]
},

Conclusion

There are a few more details that you will need to handle to have this all going exactly as I have it on my site,
such as layouts, css imports... I'll have this all posted up as a ready to use Nuxt
blog in the github repo [https://github.com/andreliem/vuecms]

Stay on top of VueJS.
Subscribe to the newsletter
and save time.