Skip to content

4. Example [nuxt-01]: routing and navigation

We will build a series of simple examples to gradually discover how a [nuxt] application works. We will start by porting the [vuejs-11] application from the document |Introduction to the VUE framework.JS by Example|, to first discover what differentiates the code organization of a [nuxt] application from that of a [vue] application.

4.1. Project directory structure

The [vuejs-11] project was a navigation project with views:

Image

The source code tree for the [vuejs-11] project was as follows:

Image

  • [main.js] was the script executed when the [vue] application started;
  • [router.js] defined the routing rules;
  • [App.vue] was the application’s structural view. It organized the layout of the various views;
  • [Component1, Component2, Component3, Layout, Navigation] were the components used in the application’s various views;

When porting the application [vue] [1] to an application [nuxt] [2]:

  • the scripts executed at application startup must be declared in the [plugins] key of the [nuxt.config.js] file. Additionally, it is possible to separate the scripts intended for the [nuxt] server from those intended for the [nuxt] client;
  • the [App.vue] view must be installed in the [layouts] folder and renamed to [default.vue];
  • the components [Component1, Component2, Component3], which are the routing targets, must be moved to the [pages] folder. One of them, the one serving as the home page, must be renamed to [index.vue]. Here, we have renamed the files:
    • [Component1] --> [index]: displays the text [Home];
    • [Component2] --> [page1]: displays the text [Page 1];
    • [Component3] --> [page2]: displays the text [Page 2];

[nuxt] uses the contents of the [pages] folder to dynamically generate the following routes:

1
2
3
{ name :’index’, ‘path’ :’/’}
{ name :’page1’, ‘path’ :’/page1’}
{ name :’page2’, ‘path’ :’/page2’}

As a result, the [router.js] file used in the [vue] project becomes unnecessary in the [nuxt] project.

The configuration file [nuxt.config.js] will be as follows:


export default {
  mode: 'universal',
  /*
   ** Headers of the page
   */
  head: {
    title: 'Introduction à [nuxt.js]',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'ssr routing loading asyncdata middleware plugins store'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  /*
   ** Customize the progress-bar color
   */
  loading: { color: '#fff' },
  /*
   ** Global CSS
   */
  css: [],
  /*
   ** Plugins to load before mounting the App
   */
  plugins: [],
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module'
  ],
  /*
   ** Nuxt.js modules
   */
  modules: [
    // Doc: https://bootstrap-vue.js.org
    'bootstrap-vue/nuxt',
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios'
  ],
  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {},
  /*
   ** Build configuration
   */
  build: {
    /*
     ** You can extend webpack config here
     */
    extend(config, ctx) {}
  },
  // source code directory
  srcDir: 'nuxt-01',
  // router
  router: {
    // application URL root
    base: '/nuxt-01/'
  },
  // server
  server: {
    // service port, default 3000
    port: 81,
    // network addresses listened to, default localhost: 127.0.0.1
    // 0.0.0.0 = all the machine's network addresses
    host: '0.0.0.0'
  }
}
  • line 62: specify the folder containing the source code for project [dvp];
  • Line 66: Specify URL as the root directory for the [dvp] application (you can enter any value you like);
  • Line 43: Note that the [bootstrap-vue] library is referenced in the configuration;

4.2. Porting the [main.js] file

The [main.js] file from the [vuejs-11] project was as follows:


// imports
import Vue from 'vue'
import App from './App.vue'
 
// plugins
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);
 
// bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
 
// router
import monRouteur from './router'
 
// configuration
Vue.config.productionTip = false
 
// instantiation project [App]
new Vue({
  name: "app",
  // main view
  render: h => h(App),
  // router
  router: monRouteur,
}).$mount('#app')

Apart from [imports], the code does the following:

  • lines 5–11: uses the [bootstrap-vue] library. This task is now handled by the [bootstrap-vue/nuxt] module on line 43 of the [nuxt.config.js] configuration file;
  • lines 14 and 25: use of the routing file [router.js]. This task is now performed automatically by the application [nuxt] based on the directory tree of the folder [pages];
  • lines 20–26: instantiation of the application’s main view. In a [nuxt] application, the [layouts/default.vue] view serves as the main view;

The file [main.js] is no longer needed. If it had been needed, it would have been declared in the key [plugins] on line 30 of the configuration file [nuxt.config.js];

4.3. The main view [default.vue]

Image

The main view [layouts / default.vue] is as follows:


<template>
  <div class="container">
    <b-card>
      <!-- a message -->
      <b-alert show variant="success" align="center">
        <h4>[nuxt-01] : routage et navigation</h4>
      </b-alert>
      <!-- the current routing view -->
      <nuxt />
    </b-card>
  </div>
</template>
 
<script>
export default {
  name: 'App'
}
</script>
  • On line 9, in the [vuejs-11] project, we had the <router-view /> tag instead of the <nuxt /> tag used here. Both seem to work. I tried both of them without seeing any change. I kept the <nuxt /> tag, which is the recommended one. It displays the current view, i.e., the target page of the current route;

4.4. The components

Image

Compared to the [vuejs-11] project, the components in [layout, navigation] remain unchanged:

[components / layout.vue]


<!-- view layout -->
<template>
  <!-- line -->
  <div>
    <b-row>
      <!-- two-column zone -->
      <b-col v-if="left" cols="2">
        <slot name="left" />
      </b-col>
      <!-- ten-column zone -->
      <b-col v-if="right" cols="10">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>
 
<script>
export default {
  // settings
  props: {
    left: {
      type: Boolean
    },
    right: {
      type: Boolean
    }
  }
}
</script>

This component is used to structure the application's pages into two columns:

  • lines 7–9: the left column spans 2 Bootstrap columns;
  • lines 11–13: the right column spans 10 Bootstrap columns;

[navigation.vue]


<template>
  <!-- bootstrap menu with three options -->
  <b-nav vertical>
    <b-nav-item to="/" exact exact-active-class="active">
      Home
    </b-nav-item>
    <b-nav-item to="/page1" exact exact-active-class="active">
      Page 1
    </b-nav-item>
    <b-nav-item to="/page2" exact exact-active-class="active">
      Page 2
    </b-nav-item>
  </b-nav>
</template>

This component displays three links from navigation:

Image

To determine the values for the [to] attributes in lines 4, 7, and 10, you must refer to the [pages] and [2] files:

  • the [index] page will have URL and [/];
  • the page [page1] will have URL and [/page1];
  • the page [page2] will have URL and [/page2];

The component [navigation] can also be written as follows:


<template>
  <!-- bootstrap menu with three options -->
  <b-nav vertical>
    <nuxt-link to="/" exact exact-active-class="active">
      Home
    </nuxt-link>
    <nuxt-link to="/page1" exact exact-active-class="active">
      Page 1
    </nuxt-link>
    <nuxt-link to="/page2" exact exact-active-class="active">
      Page 2
    </nuxt-link>
  </b-nav>
</template>

The <b-nav-item> tag is replaced by the <nuxt-link> tag, which denotes a routing link. In practice, I didn’t notice any major difference—nothing that would tip the scales in favor of one tag over the other.

4.5. The pages

Image

The page [index.vue] displays the following view:

Image

The page code is as follows:


<!-- main page -->
<template>
  <Layout :left="true" :right="true">
    <!-- navigation -->
    <Navigation slot="left" />
    <!-- message-->
    <b-alert slot="right" show variant="warning">
      Home
    </b-alert>
  </Layout>
</template>
 
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
 
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
 
export default {
  name: 'Home',
  // components used
  components: {
    Layout,
    Navigation
  },
  // life cycle
  beforeCreate(...args) {
    console.log('[home beforeCreate]', 'process.server=', process.server,
'process.client=', process.client, "number of arguments=", args.length)
  },
  created(...args) {
    console.log('[home created]', 'process.server=', process.server,
'process.client=', process.client, "number of arguments=", args.length)
  },
  beforeMount(...args) {
    console.log('[home beforeMount]', 'process.server=', process.server,
'process.client=', process.client, "number of arguments=", args.length)
  },
  mounted(...args) {
    console.log('[home mounted]', 'process.server=', process.server,
'process.client=', process.client, "number of arguments=", args.length)
  }
}
</script>
  • line 5: the navigation component is placed in the left column;
  • lines 7–9: an alert is placed in the right column;

In the <script> section, we add code to the lifecycle functions of the [beforeCreate, created, beforeMount, beforeMounted] page. We want to know which ones are executed by the []nuxt server and which ones by the [nuxt] client. Let’s recall two things:

  • when a page is requested either at application startup, as is the case with the [index] page, or manually by the user who refreshes the browser page or types a URL URL by hand, it is first served by the [nuxt] server. The server interprets the code above and executes the Javascript it contains;
  • when the page sent by the [nuxt] server arrives at the browser, it arrives with the client code [nuxt]. This code interprets the page above once again;
  • We use logs to track who is doing what in order to better understand this process;
  • Lines 30–31: The function uses a global object [process] that exists on both the server and the client:
    • [process.server] is true if the code is executed by the server, false otherwise;
    • [process.client] is true if the code is executed by the client, false otherwise;
    • Because the variable [process] is undeclared in the code, we are required to include line 14 for [eslint]. Line [16] is necessary because otherwise [eslint] declares a different type of error due to the variable [process]. Line 15 is necessary to allow the use of [console] in lifecycle functions;
  • Line 29: We also want to know if the lifecycle functions receive arguments. We will indeed find that [nuxt] passes information to certain functions. We want to know if the lifecycle functions are among them;
  • We repeat the same code for all four functions;

4.6. The [nuxt.config.js] file

This file controls the execution of the [dvp] project. It was described on page 33.

4.7. Project Execution

We run the project:

Image

The page displayed is as follows:

Image

Once installed in the browser, the [nuxt] application becomes a standard [vue] application. We will therefore not discuss the client-side behavior of the [nuxt-01] application. This was covered in the [vuejs-11] project in the document |Introduction to the VUE.JS Framework by Example|.

The [nuxt] application differs from the [vue] application in only two respects:

  • the initial launch of the application, which displays the home page;
  • whenever the user triggers a browser refresh in any way;

In both cases:

  • the requested page is provided by the server;
  • the received page is processed by the client;

Let’s look at the application startup logs (F12 in the browser):

Image

  • in [1], the server logs (process.server=true). They appear preceded by the entry [Nuxt SSR] (SSR= Server Side Rendered);
  • in [2], the client logs in the browser (process.client=true);

From these logs, we can deduce that:

  • the server executes the [beforeCreate, created] lifecycle functions;
  • the client executes the [beforeCreate, created, beforeMount, mounted] lifecycle functions;
  • the server processed the page before the client;
  • in both cases, none of the executed functions receive any arguments;

Now let’s look at the source code of the received page (option [Code source de la page] in the browser):


<!doctype html>
<html data-n-head-ssr>
<head>
  <title>Introduction à [nuxt.js]</title>
  <meta data-n-head="ssr" charset="utf-8">
  <meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
  <meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
  <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
  <base href="/nuxt-01/">
  ....
  <link rel="preload" href="/nuxt-01/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/app.js" as="script">
</head>
<body>
  <div data-server-rendered="true" id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
              <h4>[nuxt-01] : routage et navigation</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-01/" target="_self" class="nav-link active nuxt-link-active">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page2" target="_self" class="nav-link">
                        Page 2
                      </a>
                    </li>
                  </ul>
                </div> <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-warning">
                    Home
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
    window.__NUXT__ = (function (a, b, c, d, e, f, g, h, i, j) {
      return {
        layout: "default", data: [{}], error: null, serverRendered: true,
        logs: [
          { date: new Date(1574069600078), args: [a, b, c, d, e, f, g, "(repeated 1 times)"], type: h, level: i, tag: j },
          { date: new Date(1574070938091), args: [a, b, c, d, e, f, g], type: h, level: i, tag: j }
        ]
      }
    }("[home beforeCreate]", "process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0", "log", 2, ""));
  </script>
  <script src="/nuxt-01/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-01/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/app.js" defer></script>
</body>
</html>

Comments

  • The first thing that can be noted is that the received HTML code correctly reflects what the user sees. This was not the case with the [vue] applications, for which the displayed source code was the source code of a nearly empty HTML file. That was what the browser had received. Then the [vue] client took over and built the page the user expected. You then had to go to the [inspecteur] tab in the browser’s developer tools (F12) to view the HTML code of the displayed page;
  • lines 57–67: this is the script that displayed the logs tagged [Nuxt SSR]. These logs were generated on the server side, and the results were embedded in a script included in the page sent;
  • lines 68–71: the scripts that form the client-side code executed in the browser;

The scripts in lines 68–71 are executed and transform the received page. To see the page ultimately displayed to the user, go to the [inspecteur] tab in the browser’s developer tools (F12):

Image

When you expand the <html> tag, you see the following content:


<head>
  <title>Introduction à [nuxt.js]</title>
  <meta data-n-head="ssr" charset="utf-8">
  <meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
  <meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
  <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
  <base href="/nuxt-01/">
  ...
  <link rel="preload" href="/nuxt-01/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-01/_nuxt/app.js" as="script">
 
  <script charset="utf-8" src="/nuxt-01/_nuxt/pages_index.js"></script>
  <script charset="utf-8" src="/nuxt-01/_nuxt/pages_page1.js"></script>
  <script charset="utf-8" src="/nuxt-01/_nuxt/pages_page2.js"></script>
</head>
<body>
  <div id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-success" align="center">
              <h4>[nuxt-01] : routage et navigation</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-01/" target="_self" class="nav-link active nuxt-link-active">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page2" target="_self" class="nav-link">
                        Page 2
                      </a>
                    </li>
                  </ul>
                </div>
                <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-warning">
                    Home
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
    window.__NUXT__ = (function (a, b, c, d, e, f, g, h, i) {
      return {
        layout: "default", data: [{}], error: null, serverRendered: true,
        logs: [
          { date: new Date(1574068674481), args: ["[home beforeCreate]", a, b, c, d, e, f], type: g, level: h, tag: i },
          { date: new Date(1574068674482), args: ["[home created]", a, b, c, d, e, f], type: g, level: h, tag: i }
        ]
      }
    }("process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0", "log", 2, ""));
  </script>
  <script src="/nuxt-01/_nuxt/runtime.js" defer=""></script>
  <script src="/nuxt-01/_nuxt/commons.app.js" defer=""></script>
  <script src="/nuxt-01/_nuxt/vendors.app.js" defer=""></script>
  <script src="/nuxt-01/_nuxt/app.js" defer=""></script>
 
  <iframe id="mc-sidebar-container" ...></iframe>
  <iframe id="mc-topbar-container"...>  </iframe>
  <iframe id="mc-toast-container" ...></iframe>
  <iframe id="mc-download-overlay-container"...></iframe>
</body>

Comments

  • At first glance, the page displayed in lines 19–59 appears to be the same as the received page;
  • Lines 14–16: Three new scripts appear, one for each page of the application;
  • Lines 76–79: Four [iframe] appear;

Lines 33, 37, and 42: the links are problematic. They appear to be normal links that, when clicked, will send a request to the server. However, upon execution, we see that this is not true: no request is sent to the server. To understand why, go back to the [inspecteur] tab in the browser:

Image

We see that in [1, 2], events have been attached to the links. It is the scripts on lines 71–74 that attached event handlers to the links. Therefore:

  • the page displayed by the client is visually identical to the one sent by the server;
  • dynamic behavior has been added to the page by the client;

Now let’s request the page [page1] by manually typing URL. The logs become as follows:

Image

We get the same results as for the page [index] but for [page1]. The source code of the received page is as follows:


<body>
  <div data-server-rendered="true" id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success"><h4>[nuxt-01] : routage et navigation</h4></div> <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-01/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page1" target="_self" class="nav-link active nuxt-link-active">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-01/page2" target="_self" class="nav-link">
                        Page 2
                      </a>
                    </li>
                  </ul>
                </div> <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-primary">
 
                    Page 1
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>window.__NUXT__ = { layout: "default", data: [{}], error: null, serverRendered: true, logs: [{ date: new Date(1573917721122), args: ["[page1 beforeCreate]", "process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0"], type: "log", level: 2, tag: "" }] };</script>
  <script src="/nuxt-01/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-01/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-01/_nuxt/app.js" defer></script>
</body>

We get the same type of page as the [index] page but with the alert from the [Page 1] view (line 30). Lines 41–44: the client code was sent back with the page. Ultimately, manually requesting a URL is identical to restarting the application. The only difference is that the page displayed is not necessarily the home page; it is the one that was requested. Once the page is received, the client takes over. The server will no longer be contacted unless the user decides otherwise.