Simple Guide to Nuxt.js (Vue.js SSR)

Feb 14, 2020

Why use Nuxt

I wanted to do Vue.js Server-Side Rendering (SSR). After reading into https://ssr.vuejs.org/ and exploring https://github.com/vuejs/vue-hackernews-2.0/, I found that there are a lot of codes required to piece things together, and decide to explore Nuxt.js. I try to keep to vanilla Vue.js without touching Vuex or Nuxt.js for quite a while (as more frameworks means more learning and more problems to solve), but I guess there is more pros for using Vuex in the case of SSR.

  • Nuxt.js support something called Universal mode, which generate both SSR and client side code.
  • It also support static site generation, like VuePress or Hugo.
  • SSR just work out of the box, though you need to be aware of some SSR caveats while coding.
  • Using Nuxt.js means I won't be using Vue Cli, but luckily their tooling and hot-reload is pretty good as well.

SSR Caveats

  • For AJAX, use library like axios as it support server and client side fetch. Fetch API work on client-side (browser only).
  • On server-side, reactivity is disabled. It renders the component default state only.
  • Lifecyle Hook: beforeCreate and created is used (beforeMount and mount is executed on client only); Avoid global side effects code (such as setInterval in beforeCreate - created, do them at mounted and destroyed instead)
  • Avoid access to browser API like window and document
  • Some 3rd party library might not be written with universal (server and client-side) usage in mind
  • Probably want to use Webpack for both client and server code, due to vue-loader, file-loader, css-loader, javascript features (Babel), etc.

Setup Nuxt.js Project

npx create-nuxt-app PROJECTcd PROJECT

NOTE: Previous vue init nuxt-community/starter-template is deprecated.

create-nuxt-app v2.14.0
✨  Generating Nuxt.js project in journey-nuxt
? Project name PROJECT
? Project description My superior Nuxt.js project
? Author name desmond
? Choose the package manager Npm
? Choose UI framework Bootstrap Vue
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios, Progressive Web App (PWA) Support
? Choose linting tools None
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools jsconfig.json (Recommended for VS Code)

NOTE: I setup with Axios, BootstrapVue and Universal (SSR) rendering mode.

Setup Page / Route

Edit pages/index.vue, which is the main home page.

<template>  <div class="mb-3">      <h1>{{ title }}</h1>      <div>{{ description }}</div>    <button type="button" class="btn btn-primary" @click="showToast">Show Toast</button>  </div>  <pagination :total-page="totalPage" :current-page="page" /></template><script>import Pagination from '~/components/Pagination.vue'export default {  head() {    return {      title: 'Home',      meta: [        { hid: 'description', name: 'description', content: 'This is home page' }      ]    }  },  async asyncData ({ $axios }) {    const { data } = await $axios.get('/data/home.json')    /*    return {      title: data.title,      description: data.desscription    }     */    return data  },  data() {    return {      page: 1,      totalPage: 3    }  },  methods: {    showToast() {      this.$toast.show('Hello from Toast')    }  },  components: {    Pagination  }}</script>

asyncData is called before the component is rendered, which return data is merged into components's data.

head is for page title and other meta tags.

About Page

Edit pages/about.vue

<template>  <div>    <h1>About</h1>  </div></template><script>export default {  head() {    return {      title: 'About'    }  }}</script>

Pages

Page component which receive param id.

Edit pages/page/_id.vue

<template>  <div>    <div class="mb-3">      <h1>Page {{ page }}</h1>      <div v-for="value in items" :key="value">        {{ value }}      </div>    </div>    <pagination :total-page="totalPage" :current-page="page" />  </div></template><script>import Pagination from '~/components/Pagination.vue'export default {  head() {    return {      title: `Page ${this.page}`    }  },  validate ({ params }) {    // Must be a number    return /^\d+$/.test(params.id)  },  async asyncData ({ $axios, params }) {    const { data } = await $axios.get(`/data/page-${params.id}.json`)    return data  },  data() {    return {      totalPage: 3,    }  },  computed: {    page() {      return parseInt(this.$route.params.id)    }  },  components: {    Pagination  }}</script>

Header / Meta tag

Edit nuxr.config.js.

export default {  // ..  head: {    // title formatting    titleTemplate: '%s - My App Name',    meta: [      { charset: 'utf-8' },      // for bootstrap      { name: 'viewport', content: 'width=device-width, initial-scale=1, shrink-to-fit=no' },    ],    link: [      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }    ]  },  // ..}

Static Json File

Edit static/data/home.json

{  "title": "Home",  "description": "This is Home"}

Edit static/data/page-2.json

{  "title": "List of Fruit",  "items": ["Apple", "Orage", "Pear"]}

Edit static/data/page-3.json

{  "title": "List of Friends",  "items": ["Mei Ru", "Jack", "John"]}

Components

Edit components/Pagination.vue

Import this Pagination component.

Replace all <a> with <router-link> and :href with :to.

Layout

This is the default layout used by all pages component.

Edit layouts/default.vue

<template>  <div>    <notifications />    <!-- <notifications group="default" /> -->    <b-navbar toggleable="lg" type="dark" variant="info">      <div class="container">        <b-navbar-brand to="/">APP_NAME</b-navbar-brand>        <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>        <b-collapse id="nav-collapse" is-nav>          <b-navbar-nav>            <b-nav-item to="/about">About</b-nav-item>          </b-navbar-nav>        </b-collapse>      </div>    </b-navbar>    <div class="container" style="padding-top: 16px;">      <nuxt/>    </div>  </div></template><style>html {  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',    Roboto, 'Helvetica Neue', Arial, sans-serif;  font-size: 16px;  word-spacing: 1px;  -ms-text-size-adjust: 100%;  -webkit-text-size-adjust: 100%;  -moz-osx-font-smoothing: grayscale;  -webkit-font-smoothing: antialiased;  box-sizing: border-box;}*,*:before,*:after {  box-sizing: border-box;  margin: 0;}</style>

WARN: The generated pages/index.vue have a container style which mess up the container CSS of Bootstrap.

Install Vue Plugins

We install a plugin for notification.

npm install --save @nuxtjs/toast

NOTE: Not all vue components are compatible with Nuxt.js/SSR due to the confitions of SSR Caveats mentioned above. Refer to a list of Nuxt.js Modules.

NOTE: Refer to Nuxt.js Install Vue.js Plugins for non-Nuxt modules.

Edit nuxt.config.js.

export default {  // ..  modules: [    // ..    '@nuxtjs/toast'  ],  toast: {    position: 'top-center',    duration: 3000  },  // ...}

NOTE: The usage to toast plugin is demonstrated in pages/index.vue as shown above.

Local Development

npm run dev

Access: http://localhost:3000/

Deployment

Refer Deploy Nuxt.js (SSR) to Firebase Cloud Functions

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.