Back button to all articlesAll articles

Nuxt.js cheat sheet

<br>

Nuxt.js is here to make your life easy, it's also here to make the Vue.js development process even nicer than it already is. But with all its good aspects, it has quirks that will have you click on every single link on Google.

This article is here to avoid these situations, it'll cover some normal use-cases and some edge-cases with quick and easy code snippets. It won't go into extreme detail on these matters, but will give you the documentation necessary to do so in case you want to.

Note: You'll need a good grasp of Vue.js concepts to take full advantage of this article !

Before we get into anything concrete, let me explain what Nuxt.js is.

What's Nuxt.js?

Nuxt.js is a framework based on Vue.js that allows you to build fully fledged server-rendered applications.

It comes out of the box with loads of useful packages:

  • đŸ’ģ Vue
  • ↩ī¸ Vue Router (for easy routing)
  • 💾 Vuex (for easy state management)
  • 🏎 Vue Server Renderer (for server-side rendering out of the box)
  • đŸ•ĩī¸â€â™‚ī¸ Vue meta (for SEO)

Here's a list of what we'll cover (feel free to come back here if you're searching for something specific):

General

Routing

State management

SEO

Miscellaneous

If you have any other requests or want to add anything new, please feel free to hit me up on Twitter @christo_kade !

Creating a Nuxt.js project

1yarn create nuxt-app <project-name> 2

Which will prompt you to answer some questions, including:

  • Choose between integrated server-side frameworks (None by default, Express, Koa etc.)
  • Choose features to install (PWA Support, Linter / Formatter, Prettier, Axios)
  • Choose your favorite UI framework (None by default, Bootstrap, Vuetify, Bulma etc.)
  • Choose your favorite testing framework (None, Jest, AVA)
  • The Nuxt mode you want (Universal or SPA, more information)

Once done and your dependencies are installed:

1$ cd <project-name> 2$ yarn dev 3
<br>

Documentation

Testing with Nuxt.js

The majority of your testing syntax will depend on the testing framework chosen during the project's creation.
Out of the box, Nuxt uses the @vue/test-utils package to render your components thanks to multiple methods such as mount(), shallowMount() and render(). You'll then be able to test that specific values have been displayed, that specific methods were called etc.

Nuxt will also make sure to set everything up for you, all you'll have to do is create your *.spec.js or *.test.js files and run the yarn test command.

Here's a classic (and brief) example of unit testing for a Vue component in a Nuxt project:

1import { shallowMount } from "@vue/test-utils"; 2import cmp from "~/components/navbar/navbar"; 3 4// Mocking an icon displayed in my navbar 5jest.mock("~/static/icons/svg/icon-menu.svg", () => ""); 6 7describe("Navbar component", () => { 8 // We shallow mount the component while mocking some internal elements 9 // Most of the time, you'll have to mock context objects such as $store or $route in order to render your component whithout any errors 10 const wrapper = shallowMount(cmp, { 11 // Stubbing nuxt-links in the navbar 12 stubs: ["nuxt-link"], 13 mocks: { 14 "nuxt-Link": true, 15 // Mocking the $store context object 16 $store: { 17 state: { 18 locale: "en", 19 }, 20 }, 21 // Mocking the $route context object 22 $route: { 23 path: "mockedPath", 24 }, 25 }, 26 }); 27 28 it("Snapshot testing", () => { 29 expect(wrapper.html()).toMatchSnapshot(); 30 }); 31 32 describe("Components validation", () => { 33 it("should return a valid component", () => { 34 expect(wrapper.is(cmp)).toBe(true); 35 }); 36 }); 37 38 describe("Content validation", () => { 39 it("should render the link's name", () => { 40 expect(wrapper.html()).toContain("About"); 41 }); 42 43 // ... 44 }); 45}); 46
<br>

Documentation

Creating a new route

In the /pages folder, create a file, its name will be the name of the route.

So for example:

1// /pages/about.vue 2 3<template> 4 <main> 5 <h1>About page</h1> 6 <main/> 7</template> 8 9<script> 10export default {} 11</script> 12 13<style></style> 14

Navigating to localhost:3000/about will display this component's content

<br>

Documentation

Creating dynamic routes

In the /pages folder, create a directory and a file prefixed by an underscore.

For example, the following file tree:

1pages/ 2--| users/ 3-----| _id.vue 4--| index.vue 5

Will automatically generate the following router inside the .nuxt folder whenever you build your project:

1router: { 2 routes: [ 3 { 4 name: "index", 5 path: "/", 6 component: "pages/index.vue", 7 }, 8 { 9 name: "users-id", 10 path: "/users/:id?", 11 component: "pages/users/_id.vue", 12 }, 13 ]; 14} 15

You can now navigate to /users/:id, with id being whatever value you need it to be.

To retrieve this value in your _id.vue component, just do the following:

1// $route is a Nuxt context object, more info: https://nuxtjs.org/api/context 2const { id } = this.$route.params; 3
<br>

Documentation, including nested routes and dynamic nested routes.

Navigating to a route in a component template

Inside of any of your components:

1// /components/example.vue 2 3// Clicking on this nuxt-link will navigate to the /pages/about.vue component 4// nuxt-link renders an <a> tag in your HTML 5<template> 6 <section> 7 <nuxt-link to="/about">About</nuxt-link> 8 </section> 9</template> 10 11// ... 12
<br>

Documentation

Navigating to a route programatically

1// Will add a history entry to the stack 2this.$router.push({ 3 path: "/about", 4}); 5 6// Will not 7this.$router.replace({ 8 path: "/about", 9}); 10 11// Goes back one record 12this.$router.go(-1); 13

<a name="new-store"></a>

Creating a new store module

In the /store folder, each file is a Vuex module.

1// /store/todos.js 2export const state = () => ({ 3 list: [], 4}); 5 6export const mutations = { 7 add(state, text) { 8 state.list.push({ 9 text: text, 10 done: false, 11 }); 12 }, 13 remove(state, { todo }) { 14 state.list.splice(state.list.indexOf(todo), 1); 15 }, 16 toggle(state, todo) { 17 todo.done = !todo.done; 18 }, 19}; 20

Each module's mutations, actions & states are now available using the context object $store:

1// /components/todo.vue 2<template> 3 <ul> 4 <li v-for="todo in todos"> 5 <input type="checkbox" :checked="todo.done" @change="toggle(todo)" /> 6 <span>{{ todo.text }}</span> 7 </li> 8 <li> 9 <input placeholder="What needs to be done?" @keyup.enter="addTodo" /> 10 </li> 11 </ul> 12</template> 13 14<script> 15 import { mapMutations } from "vuex"; 16 17 export default { 18 computed: { 19 todos() { 20 return this.$store.state.todos.list; // highlight-line 21 }, 22 }, 23 methods: { 24 addTodo(e) { 25 this.$store.commit("todos/add", e.target.value); // highlight-line 26 e.target.value = ""; 27 }, 28 ...mapMutations({ 29 // highlight-line 30 toggle: "todos/toggle", // highlight-line 31 }), // highlight-line 32 }, 33 }; 34</script> 35
<br>

Documentation

Updating a store before rendering a component

Sometimes you need to fill up a given state variable before rendering a component, here's how:

1// In any component 2 3export default { 4 // Called before rendering the component 5 fetch({ store, params }) { 6 return axios.get("https://dog.ceo/api/breeds/image/random").then((res) => { 7 store.commit("setDog", res.data.message); 8 }); 9 }, 10}; 11

Warning: You don't have access of the component instance through this inside fetch because it is called before initiating the component (read more).

<br>

Documentation

Changing a page's head properties dynamically

For SEO purposes, defining the page's title, description keywords etc. can be useful. Here's how you can do it programmatically:

1// In any component 2export default { 3 head: { 4 title: "Page title", 5 meta: [ 6 { 7 hid: "description", 8 name: "description", 9 content: "Page description", 10 }, 11 ], 12 // ... 13 }, 14}; 15

Info: To avoid duplicated meta tags when used in child component, set up an unique identifier with the hid key for your meta elements (read more).

<br>

Documentation

SSR for dynamic routes

When running nuxt generate, the HTML file for your dynamic routes won't be generated by default.

For example, if you have an about.vue page and a _id.vue one, when running nuxt generate, the resulting dist folder will contain /about/index.html but won't generate anything for your dynamic _id.vue.

This can lead to your dynamic routes to be missed by crawlers, and therefore not referenced by search engines !

Here's how you can generate them automacially:

1// nuxt.config.js 2 3module.exports = { 4 // ... 5 6 // dynamicRoutes could be a JSON file containing your dynamic routes 7 // or could be retrieved automatically based on the content of your /pages folder 8 generate: { 9 routes: () => { 10 return dynamicRoutes.map((route) => `/articles/${route}`); 11 }, 12 }, 13 14 // ... 15}; 16

nuxt generate will now generate the HTML file for each dynamic route returned by the generate property.

<br>

Documentation

Displaying a fixed component throughout your app

Sometimes you need to add a navbar or a footer that will be displayed no matter the current route.

There's a /layout folder that contains default.vue by default. This layout holds the <nuxt/> component that takes care of rendering the content of each one of your pages (see Creating a new route).

Simply modify that component to fit your needs, for example:

1<template> 2 <div> 3 <navbar /> 4 <nuxt /> 5 <footer /> 6 </div> 7</template> 8 9<script> 10 import navbar from "~/components/navbar/navbar"; 11 import footer from "~/components/footer/footer"; 12 13 export default { 14 components: { 15 cmpNavbar, 16 cmpFooter, 17 }, 18 }; 19</script> 20
<br>

Documentation

Changing a project's router base

In some cases, when for example you're deploying your project on Github Pages under username/my-project, you'll need to change the project's router base so that your assets are displayed correctly.

1// nuxt.config.js 2 3// Will change the router base to /my-project/ when DEPLOY_ENV equals GH_PAGES 4const routerBase = 5 process.env.DEPLOY_ENV === "GH_PAGES" 6 ? { 7 router: { 8 base: "/my-project/", 9 }, 10 } 11 : { 12 router: { 13 base: "/", 14 }, 15 }; 16 17module.exports = { 18 // ... 19 routerBase, 20 // ... 21}; 22

And don't forget to change your package.json so that nuxt.config.js knows when you're building or generating for Github Pages.

1// package.json 2 3"scripts": { 4 "build:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt build", 5 "generate:gh-pages": "DEPLOY_ENV=GH_PAGES nuxt generate" 6}, 7

Handling internationalization (i18n)

Start by running yarn add vue-i18n

Create the following file:

1// /plugins/i18n.js 2 3import Vue from "vue"; 4import VueI18n from "vue-i18n"; 5 6Vue.use(VueI18n); 7 8export default ({ app, store }) => { 9 // Set i18n instance on app 10 // This way we can use it globally in our components 11 app.i18n = new VueI18n({ 12 locale: store.state.locale, 13 fallbackLocale: "fr", 14 messages: { 15 // Add the supported languages here AND their associated content files 16 en: require("~/static/json/data-en.json"), 17 fr: require("~/static/json/data-fr.json"), 18 }, 19 }); 20}; 21

And add the following line in your nuxt.config.js to inform it we're using that plugin:

1module.exports = { 2 // ... 3 plugins: ["~/plugins/i18n.js"], 4 // ... 5}; 6

In this example, the current locale is based on my store's content, which looks like so:

1export const state = () => ({ 2 locales: ["en", "fr"], 3 locale: "fr", 4}); 5 6export const mutations = { 7 setLanguage(state, locale) { 8 if (state.locales.indexOf(locale) !== -1) { 9 state.locale = locale; 10 } 11 }, 12}; 13

So whenever we call setLanguage, the locale is automatically updated and the correct JSON file is loaded ! ✨

Your file contents are now available throughout your application like so:

1// Here we access the 'users' array in our JSON file 2this.$t("users"); 3
<br>

Documentation

Importing a font to your project

1// nuxt.config.js 2 3module.exports = { 4 /* 5 ** Headers of the page 6 */ 7 head: { 8 // ... 9 link: [ 10 { 11 rel: "stylesheet", 12 href: "https://fonts.googleapis.com/css?family=Lato", 13 }, 14 ], 15 }, 16 17 // ... 18}; 19

Wrapping up

Alright, I believe that's enough for one article. I've covered lots of use-cases which hopefully will be useful to some of you.
If you have any questions or want to add anything to this article, feel free to message me on Twitter @christo_kade, and make sure to follow me to be informed of any new articles I write or fun discoveries related to Javascript & CSS 😄