Going Vue with Nuxt

Introduction

Vue.js is a solid option for building web applications.
To use it we have two major tools:

  • Vue CLI 3 the Standard Tooling for Vue.js Development
    The official solution to quickly setup a Vue application.
  • Nuxt Universal Vue.js Applications
    Yeah, we will talk about that.

But how dœs Nuxt differ from a Standard Vue Application?

[TL;DR]

[UPDATE] use now Nuxt 2

installation

There’s two ways Nuxt can be installed:

  • with vue-cli
  • a basic npm install nuxt or yarn add nuxt if you’re a yarn person, create some folders and add some modules if you want to use pre-processors

I prefer the latter one as it doesn’t rely on any global dependency… and it’s also a good way to integrate Nuxt to an existing project.

For a web application, I always add:

I’m 100% sure that at one point or another I will need them.

Having the internationalization being done as soon as possible doesn’t demand a lot of extra efforts and prevents me the boring task of including it later (going file by file and adding the i18n calls & keys…)

application structure

Vue doesn’t enforce any kind of structure but we all like & need to stay organized.

If you use Vue CLI, it will create this kind of structure:

  • vue.config.js vue configuration
  • 📁 src
    • main.js your application's entry point
    • router.js configuring routes
    • App.vue main Vue component
    • 📁 assets all static files
    • 📁 components other vue components
    • 📁 views your pages' components
    • 📁 store
      • index.js your Vuex Store

Nuxt will require something like that:

  • nuxt.config.js nuxt configuration
  • 📁 static all static files
  • 📁 pages all page files
  • 📁 layouts all layouts files (a nuxt thingy thing that we will speak about later)
  • 📁 store your Vuex Store
  • 📁 plugins Vue plugins

It’s a flatter structure with obvious names.

Comparison between Vue & Nuxt file structure

I ❤️ folders

commands

Both Vue CLI & Nuxt propose a bunch of useful commands.
I’ll just speak about the main ones. They both serve the same purpose:

  • make a quick development server to start coding
  • build for production

Vue CLI use vue-cli-service which is a local package to launch the magic.

  • vue-cli-service serve development server
  • vue-cli-service build build for production

Nuxt has the equivalent commands.
No need to install an additional module 👍

  • nuxt development server
  • nuxt build build for production

I usually make the same npm scripts aliases across all my projects:

01-npm-script.jsonview raw
1
2
3
4
5
6
{
"scripts": {
"dev": "nuxt",
"build": "nuxt build"
}
}

After that, I can do yarn dev to start coding & yarn build to export.
Those commands will stay independent of whatever the application is using underneath.

Small Nuxt overview

Nuxt relies in some part on convention over configuration.
By creating files, Nuxt will take care of integrating them in your Vue application’s.

Here are the main domains where it shines.

routing

In a standard Vue application you’ll need to manually configure the router.
This is how the router.js usually looks like:

02-vue-router.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import NotFound from './views/NotFound.vue'

Vue.use(Router)

export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/foo',
name: 'foo',
// needed for webpack's code splitting
component: () => import(/* webpackChunkName: "foo" */ './views/Foo.vue'),
},
{
path: '/bar/:id',
name: 'bar',
component: () => import(/* webpackChunkName: "bar" */ './views/Bar.vue'),
},
{
path: '*',
component: NotFound,
},
],
})

This has some drawbacks when refactoring.
If you want to rename a route, you’ll have to:

  • rename the component
  • modify the router.js file
    • change the route name
    • change the component import
    • change the webpack chunk name

With Nuxt, this routing will look like this:

  • 📁 pages
    • index.vue
    • foo.vue
    • 📁 bar
      • _id.vue

Renaming a route is now just changing a file/folder name.
And you have the page code splitting out of the box.

a Vue todo list longer than the Nuxt one

Who does not like todo lists?

store

The same goes with a standard Vuex store:

03-vuex-store.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import Vuex from 'vuex'

import * as foo from './foo'
import * as bar from './bar'

Vue.use(Vuex)

export default new Vuex.Store({
modules: {
foo,
bar,
},
plugins: [],
})

And as you’ve guessed in Nuxt it just follows the same principles as for the routing:

  • 📁 store
    • foo.js
    • bar.js

With the same advantages as the routing.
Still, there is a classic mode if you want to have more control over it.

a note on layouts

Nuxt provides a way to handle many page layouts in a breeze.

I think most of the time you’ll stick with the basic:

  • layouts/default.vue
  • layouts/error.vue

If you want to achieve this in a regular Vue application, you’ll have to do it manually by wrapping every page components inside the desired layout component… which will bloat a little bit your code.

So not a must have but definitively a nice addition 🏅.

plugins (like vue I18N)

Integrating more things from the Vue ecosystem is similar to what it is in a standard Vue application.

This is well documented here.

For example, create a i18n.js file in the plugin folder…

04-nuxt-plugin.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import VueI18n from 'vue-i18n'

import { en, fr } from '~/locales'

Vue.use(VueI18n)

export default nuxtContext => {
const { app } = nuxtContext
app.i18n = new VueI18n({
fallbackLocale: `en`,
messages: { en, fr },
})
}

…and update the nuxt.config.js

05-nuxt-config.jsview raw
1
2
3
4
5
6
7
// Nuxt 2 use ESM modules
export default {
plugins: [
// reference my plugin, so Nuxt will load it
`@/plugins/i18n.js`,
],
}

…and you can use $t('my-i18n0key') inside your app!

As for now, Nuxt doesn’t support a convention over configuration pattern for plugins’ integration 😐 so you’ll have to write some boilerplate code.
On the bright side this code is unlikely to change in the future.

But what in fact looks like an unnecessary configuration serves in fact a very important purpose.

Nuxt allows us to build universal web applications.
This means that it should be able to bundle your code:

  • for the browser
  • for the server

If you’re only targeting the browser (SPA), you don’t have to worry about it.
But if you’re running the code on the server, you don’t want it to break because of the use of some browser API.

Node.js being killed by window

window killed me!

Nuxt prevents that with a small additional configuration.

06-nuxt-config-ssr.jsview raw
1
2
3
4
5
6
7
export default {
plugins: [
`@/plugins/i18n.js`,
// remove Server Side Rendering (SSR) from this specific file
{ src: `@/plugins/browser.js`, ssr: false },
],
}

Now browser.js will be removed from the server bundle, and we’re assured that our code won’t throw because of a missing window object in the NodeJs environment 😅

Prototyping & evolution

a people playing blocks until he build a castle

that escalated quickly

In my opinion the main advantage of Nuxt is how convenient it is to make a small prototype and build upon it until a first result.
While being sure that we can make it evolve in any direction in the future.

single page application

Writing a SPA makes you able to build quite quickly a small app and give anyone the opportunity to play with it in almost real conditions.

You can make a simple static API by putting some JSON files inside the static folder and you can persist your application’s state by using the local storage api with vuex-persistedstate

Hosting solutions like firebase, netlify or github pages provide a way to share your application for a free cost.

Universal Web Application

And now that you’re satisfied with your prototype, you can push it further by integrating it to a Node server.
Nuxt provides some templates to see how integration works many frameworks:

I’ll use Koa 🐨

In a server/index.js file:

07-nuxt-koa.jsview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import Koa from 'koa'
import Router from 'koa-router'
import koaBody from 'koa-body'
import { Nuxt, Builder } from 'nuxt'

import config from '../nuxt.config.js'

startServer()

async function startServer() {
const app = new Koa()
const HOST = process.env.HOST || `127.0.0.1`
const PORT = process.env.PORT || 3000

app.use(async function handleError(ctx, next) {
try {
await next()
} catch (err) {
ctx.status = err.statusCode || err.status || 500
ctx.body = err
}
})

//----- integrate a server API

const apiRouter = new Router({ prefix: `/api` })

apiRouter.get(`/foo`, async ctx => {
ctx.body = { foo: `foo nuxt example` }
})
apiRouter.get(`/bar/:id`, async ctx => {
const { id } = req.params
ctx.body = { bar: `bar ${id}` }
})
apiRouter.post(`/bar/:id`, koaBody(), async ctx => {
const { id } = req.params
ctx.body = { bar: `bar ${id} is updated!` }
})

app.use(apiRouter.routes())
app.use(apiRouter.allowedMethods())

//----- NUXT

config.dev = !(app.env === `production`)

// Instantiate nuxt.js
const nuxt = new Nuxt(config)

// Build in development
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}

app.use(ctx => {
ctx.status = 200
ctx.respond = false
ctx.req.ctx = ctx
nuxt.render(ctx.req, ctx.res)
})

app.listen(PORT, HOST, function endInit() {
console.log(`APP Server is listening on ${HOST}:${PORT}`)
})
}

In the package.json you should update your scripts:

08-server-script.jsonview raw
1
2
3
4
5
6
7
8
{
"scripts": {
"dev": "backpack dev",
"build": "nuxt build && backpack build",
"start": "yarn build && yarn serve",
"serve": "cross-env NODE_ENV=production node build/main.js"
}
}

You will need to update a little bit your existing code

  • Take care of your store’s actions to point to your new API.
  • use backpack to run/compile your server application.
    This is mainly due to the fact that we’re using ES Modules on the server, and that NodeJS isn’t still there.

Besides that there isn’t much more to do.
Everything will work as expected.

benefits of a UWA

Building a Universal Web Application can seem unnecessary but it comes with some advantages:

  • Better initial rendering time
  • Can make an application that works without browser Javascript
  • Should have a better SEO (you can read more about SEO here)

If you want to read more about this subject you can check Stereobooster’s article about Server Side Rendering pros and cons.

And also…

This post isn’t an exhaustive list of what Nuxt can offer you.
Here’s a quick list of other things that it provides:

Conclusion

Nuxt doesn’t have a beautiful GUI 😶 but it’s a very clever piece of code that makes me feel more productive while building a website or an application.

I’ve made a small demo repository with almost the same code as used in this post.

If you want to learn more, here are some useful links I’ve came across recently:

So if you’re using Vue, you might want to try Nuxt.