Nuxt.JS – Pourquoi préférer utiliser Vue.js via Nuxt.js

  • Bastien 

« Pourquoi utiliser un framework (Nuxt.js) pour utiliser un framework (Vue.js) ? »
Je tenterai de répondre à cette question en vous en posant une autre puis en argumentant: Pourquoi Nuxt.js devient indispensable dans le cadre d’un projet développé en Vue.js ?

Cet article suppose que vous possédiez déjà des notions en SPA (Single Page Application) et plus spécifiquement en Vue.js.

Qu’est ce que Nuxt.js?

Tandis que Vue.js et Vue CLI facilitent déjà beaucoup la configuration d’un projet et le développement d’application web au travers de son approche progressive, Nuxt.js ajoute à cela une logique puissante d’abstraction et de rendu côté serveur.

Nuxt.js est un framework qui permet de créer des applications universelles. Une application universelle se différencie des applications web classiques par sa capacité à pouvoir s’exécuter aussi bien côté client que côté serveur. Cela ouvre la possibilité de pouvoir construire une page côté serveur puis de l’envoyer déjà constituée au client.

Nuxt.js offre énormément de confort pour très peu de frais. En effet, pour un projet comprenant VueJS, Vue Router, Vuex, Vue Server Renderer et Vue-Meta, la taille en byte du bundle s’élève à seulement 60k et le tout sans aucune configuration de votre part.

Beaucoup de frameworks Javascript modernes, comme Vue.js, ont pour but de construire des applications de type SPAs (Single Page Applications). En effet elles offrent une meilleure expérience utilisateur par rapport aux sites traditionnels, qui s’explique par exemple par un meilleur caching des ressources ou encore la séparation serveur-client qui rend la navigation plus fluide.

Bien-sûr ces SPAs ont aussi quelques désavantages et notamment le temps de chargement initial (chargement du script JS, exécution de ce dernier, construction de la page à l’aide du JS …) ou encore ses mauvais résultats en termes de référencement, dû au fait que la page est initialement sans contenu et surtout sans meta-données (title, header).


Une SPA a besoin de temps pour s’initialiser et rendre correctement une page.

Abstraction de concepts Vue avec Nuxt.js

La philosophie derrière Vue.js est simple : adoptez-le de la manière qui vous convient. En effet, il se positionne au-dessus de toute opinion que ce soit en termes de structure ou de langage (TS, JS, JSX …) par exemple. L’avantage évident est la liberté avec laquelle on s’approprie les concepts de Vue.js et leur implémentation. Par contre, le désavantage que l’on remarque tout de suite est le manque de standards et de bonnes pratiques.

Nuxt.js va, au travers de son auto-configuration, abstraire plusieurs concepts afin de nous fournir une base intéressante de projet. Ce qui suit est la configuration par défaut de Nuxt.js; sachez cependant que tout est configurable à votre guise. Ce qui suit n’est également pas impossible avec Vue seul, Nuxt.js va simplement fournir ces concepts par défaut lorsque Vue.js vous laissera implémenter ces concepts.

Le Routing

Nuxt.js offre la possibilité de créer nos pages Vue.js dans le dossier pages qui se trouve à la racine du projet. Lorsque l’on créé un fichier Vue.js dans le dossier pages, Nuxt.js va automatiquement extraire son nom et l’utiliser pour créer notre fichier de configuration du routeur.

Ainsi la structure du dossier pages devient la suivante :

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

Nuxt.js va ensuite générer le fichier de configuration suivant :

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

Ceci est totalement transparent et ne nécessite aucune intervention de votre part. Il vous suffira simplement d’apprécier l’abstraction qu’offre Nuxt.js.

Si la question des routes dynamiques vous vient, sachez que cela reste évidemment possible avec Nuxt.js. Pour en savoir plus, consultez la page suivante.

Le Store

Nuxt.js va automatiquement vérifier l’existence du dossier store et si il existe va :

  1. Importer Vuex
  2. Ajouter le store à l’instance racine de Vue

Vous aurez le choix entre un des deux modes d’écriture du store :

  1. Modules : Tous les fichiers .js à l’intérieur du dossier store seront transformés en « namespace module« .
  2. Classic : store/index.js va retourner une méthode qui créé une instance du store.

La méthode classique étant dépréciée, je vous conseille de partir sur l’écriture de votre store en mode module comme ci-dessous avec un fichier todos.js :

export const state = () => ({
  list: []
})

export const mutations = {
  add (state, text) {
    state.list.push({
      text: text,
      done: false
    })
  },
  remove (state, { todo }) {
    state.list.splice(state.list.indexOf(todo), 1)
  },
  toggle (state, todo) {
    todo.done = !todo.done
  }
}

Nuxt.js va ensuite automatiquement créer ceci :

new Vuex.Store({
  modules: {
    todos: {
      namespaced: true,
      state: () => ({
        list: []
      }),
      mutations: {
        add (state, { text }) {
          state.list.push({
            text,
            done: false
          })
        },
        remove (state, { todo }) {
          state.list.splice(state.list.indexOf(todo), 1)
        },
        toggle (state, { todo }) {
          todo.done = !todo.done
        }
      }
    }
  }
})

Pour plus d’info sur Vuex et son implémentation, veuillez vous reporter à la documentation officielle.

La mise en page (layout)

Les layouts de Nuxt.js sont de bons outils lorsque vous souhaitez modifier l’apparence de votre application Nuxt.js. Vous pouvez ainsi inclure une barre latérale ou avoir des dispositions distinctes pour mobile et bureau.

Nuxt.js vous permet d’étendre la mise en page principale ou de créer des mises en page personnalisées en les ajoutant dans le répertoire layouts.

Un layout est en fait un fichier .vue placé dans le dossier layouts. Il permet d’appliquer une mise en page à vos vues de la manière suivante :

<template>
  <div>
    <div>Ma navigation de blog est ici</div>
    <nuxt/>
  </div>
</template>

Ne pas oublier d’ajouter le composant lors de la création du layout afin d’afficher votre page à l’intérieur.

Nuxt.js et le rendu côté serveur

Si Nuxt.js abstrait de manière efficace plusieurs concepts de Vue, son principal but est d’abstraire la distribution entre le client et le serveur.

Nuxt.js offre 3 modes de rendu :

  1. le mode SPA
  2. le mode universel
  3. le mode statique (pré-rendu)

A partir du moment où vous utilisez les concepts de Nuxt.js tels que la pagination avec le dossier pages ou le store avec le dossier store vous pouvez changer de mode à tout moment, et ce, très facilement puisqu’il suffit de changer une propriété :

const pkg = require('./package')

module.exports = {
     mode: 'spa', // 'universal'
     head: {},
     loading : {},
     ...
}

S’agissant du mode statique, il suffira de lancer la commande de build nuxt generate, peu importe le mode, mais je reviendrai sur ce point précis.

Mode SPA

Le mode SPA qu’offre Nuxt.js permet d’activer le comportement classique des applications de Vue.js à savoir celui des applications monopages.

Mode Universel

Le mode universel qu’offre Nuxt.js sa caractérise d’abord par le fait qu’une instance de Node.js est nécessaire. En effet le mode universel ou rendu côté serveur permet de construire une page Vue côté serveur, à la demande d’un client, puis de la rendre au client. Cela veut dire que la page retournée par le serveur est déjà construite donc aucune exécution d’un code JavaScript n’est nécessaire pour monter des éléments dans le DOM. De plus, de cette manière un robot est capable de lire correctement les méta-données de la page et de l’indexer correctement. Enfin, plus de flash blanc à l’initialisation de la page, et plus de temps de chargement car la page vient entièrement construite :

<!doctype html>
<html data-n-head-ssr data-n-head="">
 
<head data-n-head="">
    <title data-n-head="true">nuxt-tuto</title>
    <meta data-n-head="true" charset="utf-8" />
    <meta data-n-head="true" name="viewport" content="width=device-width, initial-scale=1" />
    <meta data-n-head="true" data-hid="description" name="description" content="Nuxt.js project" />
    <link data-n-head="true" rel="icon" type="image/x-icon" href="/favicon.ico" />
    <link rel="preload" href="/_nuxt/runtime.js" as="script" />
    <link rel="preload" href="/_nuxt/commons.app.js" as="script" />
    <link rel="preload" href="/_nuxt/vendors.app.js" as="script" />
    <link rel="preload" href="/_nuxt/app.js" as="script" />
    <style data-vue-ssr-id="17cfdfa9:0">
        .nuxt-progress {
            position: fixed;
            top: 0px;
            left: 0px;
            right: 0px;
            height: 2px;
            width: 0%;
            opacity: 1;
            transition: width 0.1s, opacity 0.4s;
            background-color: #3B8070;
            z-index: 999999;
        }
         
        .nuxt-progress.nuxt-progress-notransition {
            transition: none;
        }
         
        .nuxt-progress-failed {
            background-color: red;
        }
    </style>
    <style data-vue-ssr-id="aab9a468:0">
        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;
        }
         
        .button--green {
            display: inline-block;
            border-radius: 4px;
            border: 1px solid #3b8070;
            color: #3b8070;
            text-decoration: none;
            padding: 10px 30px;
        }
         
        [...]
    </style>
</head>
 
<body data-n-head="">
    <div data-server-rendered="true" id="__nuxt">
        <!---->
        <div id="__layout">
            <div>
                <section class="container">
                    <div>
                        <div class="VueToNuxtLogo">
                            <div class="Triangle Triangle--two"></div>
                            <div class="Triangle Triangle--one"></div>
                            <div class="Triangle Triangle--three"></div>
                            <div class="Triangle Triangle--four"></div>
                        </div>
                        <h1 class="title">
      nuxt-tuto
    </h1>
                        <h2 class="subtitle">
      Nuxt.js project
    </h2>
                        <div class="links">
                            <a href="https://nuxtjs.org/" target="_blank" class="button--green">Documentation</a>
                            <a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey">GitHub</a>
                        </div>
                    </div>
                </section>
            </div>
        </div>
    </div>
    <script>
        window.__NUXT__ = {
            layout: "default",
            data: [{}],
            error: null,
            serverRendered: true
        };
    </script>
    <script src="/_nuxt/runtime.js" defer></script>
    <script src="/_nuxt/commons.app.js" defer></script>
    <script src="/_nuxt/vendors.app.js" defer></script>
    <script src="/_nuxt/app.js" defer></script>
</body>
 
</html>

Une particularité importante du mode universel vient du fait que Nuxt.js va de manière asynchrone. Une fois que toute la page est correctement affichée, télécharger Vue.js (car oui, Vue.js a été exécuté côté serveur, pas encore côté client …). On peut le voir à l’attribut defer comme ci-dessous :

<script src="/_nuxt/commons.app.js" defer></script>

Les développeurs de Nuxt.js appellent ça une « hydratation en Vue.js« . Ainsi, la première fois où l’on accède à une application universelle Nuxt.js :

  1. Le serveur nous envoie la page demandée entièrement construite
  2. Une fois la page affichée sur notre navigateur, Vue.js sera chargé et monté
  3. Toute la suite de la navigation se passera comme si l’on était en mode SPA, plus de rendu côté serveur.

Pourquoi aurait-on besoin de cette logique un poil complexe ? Qu’est ce que cela apporte ? Là est toute la raison d’exister de Nuxt.js : optimiser et améliorer l’expérience utilisateur en combinant d’abord un rendu côté serveur qui apporte une rapidité d’affichage puis le concept de SPA qui apporte de la fluidité dans la navigation.

Exemple ici.

Mode Statique

La grande innovation de Nuxt.js vient de sa commande nuxt generate.

Lors du build de votre projet pour une mise en production par exemple, Nuxt.js générera le code HTML de chacune de vos routes pour le stocker dans un fichier. Cela a bien sûr la conséquence de ne plus avoir besoin d’instance Node.js comme avec le mode universel.

Prenons l’exemple de cette arborescence de fichier à l’intérieur du dossier pages :

-| pages/
----| about.vue
----| index.vue

Nuxt.js générera donc un dossier de build nommé dist par défaut et qui sera structuré de la manière suivante :

-| dist/
----| about/
------| index.html
----| index.html

De cette façon, vous pouvez héberger votre application web sur n’importe quel hébergement statique.

Le mode statique induit une petite réflexion à bien prendre en compte avant de l’utiliser. Prenez le cas suivant : votre application a été développée et vous fetchez un tableau de « todos » à l’initialisation. Lorsque vous allez utiliser la commande nuxt generate, Nuxt.js va créer un fichier pour chaque fichier vue dans le dossier pages et bien évidemment fetch ce fameux tableau de « todos« . Il faut bien comprendre que votre tableau de « todos«  aura été fetch au moment du build et ce tableau sera écrit en dur dans le HTML de la page qui fait l’appel. Ainsi vous pourriez vous retrouvez dans le cas où le contenu de votre page généré par Nuxt.js ne correspond pas exactement à la réalité immédiate (parce que le tableau de « todos«  a été modifié depuis la génération par exemple). Vous pouvez imaginer développer un événement de rebuild qui correspondrait à votre besoin de régénérer votre projet Nuxt.js, cependant vous comprendrez ici que le mode statique sera mieux apprécié dans un contexte de haute intensité en termes de requête client ou encore dans un contexte qui bouge peu.

Voici une rapide explication de la manière dont se déroule une navigation avec une application générée statiquement par Nuxt.js :

  1. La première fois où l’on accède à l’application le serveur nous envoie la page demandée entièrement construite car bien-sûr générée au préalable.
  2. Une fois la page affichée sur notre navigateur, Vue.js sera chargé et monté.
  3. Toute la suite de la navigation se passera comme si l’on était en mode SPA, plus de demande de page générée côté serveur.

Si je résume donc, vous avez généré votre projet Nuxt.js (qui comporte un fetch d’un tableau de « todos« ) et souhaitez accéder à la page des todos. Celle-ci sera d’abord la page générée lors du build. Vous continuez votre navigation, vous serez à ce moment là dans une SPA classique avec tout ce que cela comporte. Si maintenant vous revenez sur la page des todos vous constaterez que le fetch a de nouveau lieu et le tableau des todos sera à jour car vous l’aurez compris, plus de page générée mais une SPA classique.

Conclusion

Nuxt.js configure tout ce dont vous avez besoin pour rendre votre développement d’application Vue.js plus agréable. Au delà des fonctionnalités mises en avant dans cet article, je pourrais parler du loader offert par défaut dans Nuxt.js afin de rendre visible à l’utilisateur les requêtes envoyées ou encore le « pré-fetch«  que fait Nuxt.js lorsque vous visitez une page qui contient des liens vers d’autres pages de votre application…

Nuxt.js n’impose rien car tout est entièrement configurable à votre guise, il offre cependant une base solide de bonnes pratiques et de fonctionnalités intéressantes pour un projet d’entreprise ou personnel.

Ainsi, vous pouvez tout à fait développer votre SPA Vue.js avec Nuxt.js sans jamais vous soucier d’applications universelles et de rendu côté serveur. Il apporte ici seulement des briques optionnelles et intéressantes qui restent à tout moment de votre développement disponibles en changeant une ligne de configuration.

Voici un très bon « cheat sheet » qui liste les fonctions essentielles de Nuxt.js et leur syntaxe.

Ressources

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.