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.JS framework through examples|, 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 project involving navigation between views:

Image

The source code directory structure of 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 structuring view. It organized the layout of the different views;
  • [Component1, Component2, Component3, Layout, Navigation] were the components used in the application’s various views;

When porting the [Vue] application [1] to a [Nuxt] application [2]:

  • scripts executed when the application starts must be declared in the [plugins] key of the [nuxt.config.js] file. Additionally, it is possible to separate scripts intended for the [nuxt] server from those intended for the [nuxt] client;
  • the [App.vue] view must be placed in the [layouts] folder and renamed [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 [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',
  /*
   ** Page headers
   */
  head: {
    title: 'Introduction to [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 the Webpack configuration here
     */
    extend(config, ctx) {}
  },
  // source code directory
  srcDir: 'nuxt-01',
  // router
  router: {
    // application URL root
    base: '/nuxt-01/'
  },
  // server
  server: {
    // server port, 3000 by default
    port: 81,
    // network addresses listened to, default is localhost: 127.0.0.1
    // 0.0.0.0 = all network addresses on the machine
    host: '0.0.0.0'
  }
}
  • line 62: specify the folder containing the project's source code [dvp];
  • line 66: specify the root URL of the [dvp] application (you can enter whatever you want);
  • line 43: note that the [bootstrap-vue] library is referenced in the configuration;

4.2. Porting the [main.js] file

The [main.js] file for 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 myRouter from './router'

// configuration
Vue.config.productionTip = false

// project instantiation [App]
new Vue({
  name: "app",
  // main view
  render: h => h(App),
  // router
  router: myRouter,
}).$mount('#app')

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

  • lines 5–11: uses the [bootstrap-vue] library. This 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 is now done automatically by the [nuxt] application based on the directory structure of the [pages] folder;
  • 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 [main.js] file is no longer needed. If it had been, we would have declared it in the [plugins] key on line 30 of the [nuxt.config.js] configuration file;

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]: Routing and 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 [layout, navigation] components remain unchanged:

[components / layout.vue]


<!-- view layout -->
<template>
  <!-- line -->
  <div>
    <b-row>
      <!-- two-column area -->
      <b-col v-if="left" cols="2">
        <slot name="left" />
      </b-col>
      <!-- ten-column area -->
      <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 navigation links:

Image

To determine what values to assign to the [to] attributes in lines 4, 7, and 10, look at the [pages] folder [2]:

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

The [navigation] component 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 [index.vue] page 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
  },
  // lifecycle
  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 place code in the page lifecycle functions [beforeCreate, created, beforeMount, beforeMounted]. We want to know which ones are executed by the server []nuxt and which ones by the client [nuxt]. Let’s recall two things:

  • when a page is requested—either at application startup (as with the [index] page) or manually by the user refreshing the browser page or typing a URL by hand—it is first delivered 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 in the browser, it arrives with the [nuxt] client-side code. The client-side code then re-executes the code above;
  • We use logs to track who is doing what in order to better understand this process;
  • Lines 30–31: We use a global object [process] in the function 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 [process] variable is undeclared in the code, we are required to include line 14 for [eslint]. Line 16 is necessary because otherwise [eslint] reports a different type of error due to the [process] variable. 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 discover 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. Running the project

We run the project:

Image

The page displayed is as follows:

Image

Once loaded 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 Through Examples|.

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

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

In both of these cases:

  • the requested page is served 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 label [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 (the [Page Source] option in the browser):


<!doctype html>
<html data-n-head-ssr>
<head>
  <title>Introduction to [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]: Routing and 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">
                      
                        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", "number of 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 you might notice is that the received HTML code accurately reflects what the user sees. This was not the case with [Vue] applications, where 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 [Inspector] 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 sent page;
  • 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 that is ultimately displayed to the user, go to the [Inspector] tab in the browser’s developer tools (F12):

Image

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


<head>
  <title>Introduction to [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]: Routing and 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">
                      
                        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", "number of 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 of the application’s pages;
  • Lines 76–79: Four [iframe] elements 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 the case: no request is sent to the server. To understand why, we need to return to the browser’s [Inspector] tab:

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 [page1] page by typing the URL manually [http://192.168.1.128:81/nuxt-01/page1]. The logs become as follows:

Image

We get the same results as for the [index] page, 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 role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success"><h4>[nuxt-01]: Routing and 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", "number of 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 [Page 1] view alert (line 30). Lines 41–44: the client-side code has been sent along with the page. Ultimately, manually requesting a URL is the same as restarting the application. The only difference is that the page displayed isn’t necessarily the home page; it’s 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.