4. Ejemplo [nuxt-01]: enrutamiento y navegación
Vamos a crear una serie de ejemplos sencillos para descubrir poco a poco cómo funciona una aplicación [nuxt]. Comenzaremos por adaptar la aplicación [vuejs-11] del documento |Introducción al marco de trabajo VUE.JS con ejemplos|, para descubrir, en primer lugar, qué diferencia la organización del código de una aplicación [nuxt] de la de una aplicación [vue].
4.1. Estructura del proyecto
El proyecto [vuejs-11] era un proyecto de navegación entre vistas:

La estructura del código fuente del proyecto [vuejs-11] era la siguiente:

- [main.js] era el script que se ejecutaba al iniciar la aplicación [vue];
- [router.js] establecía las reglas de enrutamiento;
- [App.vue] era la vista estructurante de la aplicación. Organizaba el diseño de las diferentes vistas;
- [Component1, Component2, Component3, Layout, Navigation] eran los componentes utilizados en las distintas vistas de la aplicación;
En la migración de la aplicación [vue] [1] a una aplicación [nuxt] [2]:
- los scripts que se ejecutan al iniciar la aplicación deben declararse en la clave [plugins] del archivo [nuxt.config.js]. Además, es posible separar los scripts destinados al servidor [nuxt] de los destinados al cliente [nuxt];
- la vista [App.vue] debe instalarse en la carpeta [layouts] y renombrarse como [default.vue];
- los componentes [Component1, Component2, Component3], que son los destinos del enrutamiento, deben migrarse a la carpeta [pages]. Uno de ellos, el que sirve como página de inicio, debe renombrarse como [index.vue]. Aquí hemos renombrado los archivos:
- [Component1] --> [index]: muestra el texto [Home];
- [Component2] --> [page1]: muestra el texto [Page 1];
- [Component3] --> [page2]: muestra el texto [Page 2];
[nuxt] utiliza el contenido de la carpeta [pages] para generar dinámicamente las siguientes rutas:
Por lo tanto, el archivo [router.js] utilizado en el proyecto [vue] ya no es necesario en el proyecto [nuxt].
El archivo de configuración [nuxt.config.js] será el siguiente:
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) {}
},
// directorio del código fuente
srcDir: 'nuxt-01',
// enrutador
router: {
// raíz de la aplicación URL
base: '/nuxt-01/'
},
// servidor
server: {
// puerto de servicio, 3000 por defecto
port: 81,
// direcciones de red en las que escucha, por defecto localhost: 127.0.0.1
// 0.0.0.0 = todas las direcciones de red de la máquina
host: '0.0.0.0'
}
}
- línea 62: se indica la carpeta que contiene el código fuente del proyecto [dvp];
- línea 66: se indica la raíz de la aplicación [dvp] (se puede poner lo que se quiera);
- línea 43: observemos que la biblioteca [bootstrap-vue] está referenciada en la configuración;
4.2. Portación del archivo [main.js]
El archivo [main.js] del proyecto [vuejs-11] era el siguiente:
// importaciones
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'
// enrutador
import monRouteur from './router'
// configuración
Vue.config.productionTip = false
// instanciación del proyecto [App]
new Vue({
name: "app",
// vista principal
render: h => h(App),
// enrutador
router: monRouteur,
}).$mount('#app')
Además de [imports], el código realiza lo siguiente:
- líneas 5-11: uso de la biblioteca [bootstrap-vue]. Esta tarea ahora la realiza el módulo [bootstrap-vue/nuxt] de la línea 43 del archivo de configuración [nuxt.config.js];
- líneas 14 y 25: uso del archivo de enrutamiento [router.js]. Esta tarea ahora la realiza automáticamente la aplicación [nuxt] a partir del árbol de carpetas de [pages];
- líneas 20-26: instanciación de la vista principal de la aplicación. En una aplicación [nuxt], la vista [layouts/default.vue] es la que sirve como vista principal;
El archivo [main.js] ya no tiene razón de ser. Si la hubiera tenido, se habría declarado en la clave [plugins] de la línea 30 del archivo de configuración [nuxt.config.js];
4.3. La vista principal [default.vue]

La vista principal [layouts / default.vue] es la siguiente:
<template>
<div class="container">
<b-card>
<!-- un mensaje -->
<b-alert show variant="success" align="center">
<h4>[nuxt-01] : routage et navigation</h4>
</b-alert>
<!-- la vista actual de enrutamiento -->
<nuxt />
</b-card>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
- En la línea 9, en el proyecto [vuejs-11], se utilizaba la etiqueta <router-view /> en lugar de la etiqueta <nuxt /> que se usa aquí. Ambas parecen funcionar. Las probé las dos sin notar ningún cambio. Me quedé con la etiqueta <nuxt />, que es la recomendada. Muestra la vista actual, es decir, la página de destino del enrutamiento actual;
4.4. Los componentes

En comparación con el proyecto [vuejs-11], los componentes [layout, navigation] no cambian:
[components / layout.vue]
<!-- disposición de las vistas -->
<template>
<!-- línea -->
<div>
<b-row>
<!-- área de dos columnas -->
<b-col v-if="left" cols="2">
<slot name="left" />
</b-col>
<!-- área de diez columnas -->
<b-col v-if="right" cols="10">
<slot name="right" />
</b-col>
</b-row>
</div>
</template>
<script>
export default {
// parámetros
props: {
left: {
type: Boolean
},
right: {
type: Boolean
}
}
}
</script>
Este componente sirve para estructurar las páginas de la aplicación en dos columnas:
- líneas 7-9: la columna de la izquierda con 2 columnas de Bootstrap;
- líneas 11-13: la columna de la derecha con 10 columnas de Bootstrap;
[navigation.vue]
<template>
<!-- menú de Bootstrap con tres opciones -->
<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>
Este componente muestra tres enlaces de navegación:

Para saber qué valor asignar a los atributos [to] de las líneas 4, 7 y 10, hay que consultar la carpeta [pages] [2]:
- la página [index] tendrá el URL [/];
- la página [page1] tendrá las páginas URL y [/page1];
- la página [page2] tendrá las páginas URL y [/page2];
El componente [navigation] también se puede escribir de la siguiente manera:
<template>
<!-- menú Bootstrap de tres opciones -->
<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>
La etiqueta <b-nav-item> se reemplaza por la etiqueta <nuxt-link>, que designa un enlace de enrutamiento. Al ejecutarlo, no noté una gran diferencia, nada que pudiera inclinar la balanza hacia una etiqueta en lugar de la otra.
4.5. Las páginas

La página [index.vue] muestra la siguiente vista:

El código de la página es el siguiente:
<!-- página principal -->
<template>
<Layout :left="true" :right="true">
<!-- navegación -->
<Navigation slot="left" />
<!-- mensaje-->
<b-alert slot="right" show variant="warning">
Home
</b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-desactivar nuxt/no-env-in-hooks */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Home',
// componentes utilizados
components: {
Layout,
Navigation
},
// ciclo de vida
beforeCreate(...args) {
console.log('[home beforeCreate]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
},
created(...args) {
console.log('[home created]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
},
beforeMount(...args) {
console.log('[home beforeMount]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
},
mounted(...args) {
console.log('[home mounted]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
}
}
</script>
- línea 5: el componente de navegación se coloca en la columna de la izquierda;
- líneas 7-9: se coloca una alerta en la columna de la derecha;
En la sección <script>, insertamos código en las funciones del ciclo de vida de la página [beforeCreate, created, beforeMount, beforeMounted]. Queremos saber cuáles son ejecutadas por el servidor []nuxt y cuáles por el cliente [nuxt]. Recordemos dos cosas:
- cuando se solicita una página, ya sea al iniciar la aplicación, como es el caso de la página [index], o bien manualmente por el usuario al actualizar la página en el navegador o al escribir una URL a mano, esta es entregada primero por el servidor [nuxt]. Este interpreta el código anterior y ejecuta el JavaScript que contiene;
- cuando la página enviada por el servidor [nuxt] llega al navegador, lo hace con el código del cliente [nuxt]. Este interpreta nuevamente la página anterior;
- mediante los registros, queremos saber quién hace qué para comprender mejor este proceso;
- líneas 30-31: en la función se utiliza un objeto global [process] que existe tanto en el servidor como en el cliente:
- [process.server] es verdadero si el código lo ejecuta el servidor; falso en caso contrario;
- [process.client] es verdadero si el código lo ejecuta el cliente, falso en caso contrario;
- debido a que la variable [process] no está declarada en el código, nos vemos obligados a incluir la línea 14 para [eslint]. La línea [16] es necesaria porque, de lo contrario, [eslint] genera otro tipo de error debido a la variable [process]. ¿Es necesaria la línea 15 para permitir el uso de [console] en las funciones del ciclo de vida?
- línea 29: también queremos saber si las funciones del ciclo de vida reciben argumentos. De hecho, descubriremos que [nuxt] transmite información a ciertas funciones. Queremos saber si las funciones del ciclo de vida forman parte de ellas;
- Repetimos el mismo código para las cuatro funciones;
4.6. El archivo [nuxt.config.js]
Es este el que controla la ejecución del proyecto [dvp]. Se describió en la página 33.
4.7. Ejecución del proyecto
Ejecutamos el proyecto:

La página que se muestra es la siguiente:

Una vez instalada en el navegador, la aplicación [nuxt] se convierte en una aplicación [vue] convencional. Por lo tanto, no comentaremos el funcionamiento del lado del cliente de la aplicación [nuxt-01]. Esto ya se hizo en el proyecto [vuejs-11] del documento |Introducción al marco de trabajo VUE.JS con un ejemplo|.
La aplicación [nuxt] solo difiere de la aplicación [vue] en dos momentos:
- el inicio inicial de la aplicación, que muestra la página de inicio;
- cada vez que el usuario provoca, de una forma u otra, la actualización del navegador;
En ambos casos:
- el servidor proporciona la página solicitada;
- la página recibida es procesada por el cliente;
Veamos los registros del inicio de la aplicación (F12 en el navegador):

- en [1], los registros del servidor (process.server=true). Aparecen precedidos por la mención [Nuxt SSR] (SSR= Server Side Rendered);
- en [2], los registros del cliente en el navegador (process.client=true);
A partir de estos registros, se puede deducir que:
- el servidor ejecuta las funciones [beforeCreate, created] del ciclo de vida;
- el cliente ejecuta las funciones [beforeCreate, created, beforeMount, mounted] del ciclo de vida;
- el servidor procesó la página antes que el cliente;
- En ambos casos, ninguna de las funciones ejecutadas recibe argumentos;
Ahora veamos el código fuente de la página recibida (opción [Code source de la page] en el navegador):
<!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>
Comentarios
- Lo primero que se puede observar es que el código HTML recibido refleja correctamente lo que ve el usuario. Este no era el caso de las aplicaciones [vue], en las que el código fuente mostrado era el código fuente de un archivo HTML prácticamente vacío. Eso era lo que había recibido el navegador. Luego, el cliente [vue] tomaba el control y generaba la página que esperaba el usuario. Entonces había que ir a la pestaña [inspecteur] de las herramientas de desarrollo del navegador (F12) para descubrir el código HTML de la página mostrada;
- líneas 57-67: este es el script que mostró los registros etiquetados como [Nuxt SSR]. Estos registros se generaron del lado del servidor y los resultados se incorporaron en un script incluido en la página enviada;
- líneas 68-71: los scripts que conforman el cliente ejecutado en el navegador;
Los scripts de las líneas 68-71 se ejecutan y transforman la página recibida. Para conocer la página que finalmente se muestra al usuario, hay que ir a la pestaña [inspecteur] de las herramientas de desarrollo del navegador (F12):

Al expandir la etiqueta <html> [3], se observa el siguiente contenido:
<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>
Comentarios
- A primera vista, la página que se muestra en las líneas 19 a 59 parece ser la misma que la página recibida;
- líneas 14 a 16: aparecen tres scripts nuevos, uno por cada una de las páginas de la aplicación;
- líneas 76 a 79: aparecen cuatro [iframe];
En las líneas 33, 37 y 42, los enlaces presentan un problema. Parecen ser enlaces normales que, al hacer clic en ellos, envían una solicitud al servidor. Sin embargo, al ejecutarlos, se observa que esto no es cierto: no se envía ninguna solicitud al servidor. Para entender por qué, hay que volver a la pestaña [inspecteur] del navegador:

Se observa que en [1, 2] se han asociado eventos a los enlaces. Son los scripts de las líneas 71 a 74 los que han asociado manejadores de eventos a los enlaces. Por lo tanto:
- la página que ve el cliente es visualmente idéntica a la enviada por el servidor;
- el cliente ha agregado un comportamiento dinámico a la página;
Ahora solicitemos la página [page1] escribiendo manualmente URL en lugar de [http://192.168.1.128:81/nuxt-01/page1]. Los registros quedan así:

Se obtienen los mismos resultados que para la página [index], pero para [page1]. El código fuente de la página recibida es el siguiente:
<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>
Se obtiene el mismo tipo de página que la página [index], pero con la alerta de la vista [Page 1] (línea 30). Líneas 41-44: el código del cliente se ha devuelto junto con la página. En definitiva, solicitar una URL manualmente equivale a reiniciar la aplicación. Simplemente, la página que se muestra no es necesariamente la página de inicio, sino la que se solicitó. Una vez recibida la página, el cliente toma el control. El servidor ya no será consultado a menos que el usuario decida lo contrario.