Skip to content

5. Ejemplo [nuxt-02]: páginas del servidor y del cliente

En este proyecto, mostramos:

  • que la página generada por el cliente puede ser visualmente diferente de la recibida del servidor. Esto provoca un cambio rápido de página, perceptible para el usuario, lo que resulta perjudicial para la usabilidad de la aplicación. Por lo tanto, es una opción que conviene evitar;
  • una solución para que la página del cliente recree la misma página que la enviada por el servidor;

El proyecto [nuxt-02] se obtiene inicialmente mediante una copia del proyecto [nuxt-01].

Image

Se añade al proyecto una carpeta [store], así como dos nuevas páginas. Volveremos sobre ello más adelante.

5.1. La página [index]

5.1.1. El código de la página

El código de la página [index] queda así:


<!-- página principal -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje-->
    <b-alert slot="right" show variant="warning"> Home - value= {{ value }} </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',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[home beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[home created]')
    // solo servidor
    if (process.server) {
      this.value = 10
    }
    // cliente y servidor
    console.log('value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[home beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[home mounted]')
  }
}
</script>

Comentarios

  • línea 7: la página [index] mostrará el valor de su propiedad [value] (línea 28);
  • líneas 36-45: hay que recordar aquí que la función [created] se ejecuta tanto en el lado del servidor como en el del cliente. En las líneas 40-42, el servidor establecerá en 10 el valor de la propiedad [value]. El cliente, por su parte, no modifica este valor. Simplemente queremos saber si el cliente conserva este valor. Veremos que no es así;

5.1.2. Ejecución

Modificamos el archivo [/nuxt.config.js] para ejecutar el proyecto [nuxt-02]:


...
  // directorio del código fuente
  srcDir: 'nuxt-02',
  // enrutador
  router: {
    // raíz de los URL de la aplicación
    base: '/nuxt-02/'
  },
  // servidor
  server: {
    // puerto de servicio, 3000 por defecto
    port: 81,
    // direcciones de red a las que escucha, por defecto localhost: 127.0.0.1
    // 0.0.0.0 = todas las direcciones de red del equipo
    host: 'localhost'
  }
...

Ejecutamos el proyecto [1]:

Image

A continuación, se muestra la página [index] [2-3]. Muestra el valor [10] durante unos instantes y, a continuación, muestra el valor [0]. ¿Qué ha pasado?

Paso 1

El servidor es el primero en ejecutarse. Ejecuta el código de la página [index]:


export default {
  name: 'Home',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[home beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[home created]')
    // solo servidor
    if (process.server) {
      this.value = 10
    }
    // cliente y servidor
    console.log('value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[home beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[home mounted]')
  }
}
  • debido a la línea 23, la propiedad [value] de la línea 10 toma el valor 10;

Esto se puede comprobar consultando el código fuente de la página recibida por el navegador (opción [code source] 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-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_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-02] : page serveur, page client</h4>
            </div> <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link active nuxt-link-active">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/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 - value= 10
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>window.__NUXT__ = ....;</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • línea 46: en la página recibida, [value] tenía el valor 10;

paso 2

Sabemos que, tras la recepción de la página, los scripts de las líneas 57-60 toman el control y modifican el comportamiento de la página recibida, e incluso la información mostrada, como ocurre aquí. Estos scripts conforman el cliente, que a su vez ejecuta el código de la página [index], el mismo código que el servidor:


export default {
  name: 'Home',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[home beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[home created]')
    // solo servidor
    if (process.server) {
      this.value = 10
    }
    // cliente y servidor
    console.log('value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[home beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[home mounted]')
  }
}
  • Para entender lo que ocurre, hay que tener en cuenta que el cliente [nuxt] no ejecutará las líneas 22-24 (process.server=false);
  • en una aplicación [vue] clásica, la propiedad [value] de la línea 10 permanece en 0. Por eso, una vez que el cliente ha pasado a la página recibida, el valor mostrado pasa a ser [0];

El valor generado por el servidor [nuxt] para la propiedad [value] no ha servido para nada.

5.2. La página [page1]

5.2.1. La tienda [Vuex]

Hemos añadido una carpeta [store] al proyecto [nuxt-02]:

Image

La presencia de esta carpeta hace que, automáticamente, [nuxt] implemente un almacén [Vuex]. El archivo [index.js] es el que implementa este almacén. En este caso, el archivo [index.js] es el siguiente:


export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state, inc) {
    state.counter += inc
  }
}

[nuxt] implementa un almacén [Vuex] a partir del contenido de [index.js]:

  • líneas 1-3: definición del estado [state] del almacén. Este estado lo devuelve una función. En este caso, el estado solo tiene una propiedad: el contador de la línea 2. La función exportada debe llamarse [state];
  • líneas 5-9: las operaciones posibles sobre el estado del almacén. Se denominan [mutations]. Aquí, la mutación [increment] permite incrementar la propiedad [counter] en una cantidad [inc]. El objeto exportado debe llamarse [mutations];

El [store] Vuex implementado por [nuxt] está disponible en diferentes lugares. En las vistas, está disponible en la propiedad [this.$store].

5.2.2. El código de la página

Al igual que la página [index], la página [page1] mostrará un valor, el del contador del store de Vuex:


<!-- página 1 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje-->
    <b-alert slot="right" show variant="primary"> Page 1 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[home beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[home created]')
    // solo servidor
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[home beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[home mounted]')
  }
}
</script>

Comentarios

  • líneas 38-40: el servidor incrementará el contador en 25;
  • línea 42: tanto el servidor como el cliente mostrarán el valor del contador;
  • línea 7: se muestra el valor del contador;

Al leer este código, hay que tener en cuenta dos cosas:

  • el código que se ejecuta es el mismo tanto para el servidor como para el cliente;
  • el objeto [this] no es el mismo: hay una versión [this] en el lado del servidor y otra, [client], en el lado del cliente;

Lo que queremos saber es si el [this.$store] del servidor es el mismo que el [this.$store] del cliente. Dado que el servidor es el que se ejecuta primero (al iniciar la aplicación), esto equivale a preguntarse: ¿se transmite al cliente el [store] inicializado por el servidor?

5.2.3. Ejecución

Ejecutamos el proyecto [nuxt-02] y tecleamos [localhost:81/nuxt-02/page1] manualmente para que se solicite al servidor. Al igual que al inicio con la página [index]:

  • el servidor ejecuta la página [page1.vue];
  • envía la página generada al navegador. Esta se muestra;
  • los scripts de cliente integrados en la página enviada toman el control y vuelven a ejecutar la página [page1.vue];
  • la página mostrada se modifica entonces;

El resultado final es el siguiente:

Image

Esta vez, el valor mostrado es efectivamente el establecido por el servidor y, visualmente, no se aprecia que la página «titubee» debido a un cambio por parte del cliente del valor mostrado por el servidor. ¿Qué ha ocurrido esta vez?

El servidor ha ejecutado la siguiente página [page1]:


...

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page1 beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[page1 created]')
    // solo servidor
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page1 mounted]')
  }
}
</script>
  • las líneas 30-32 se ejecutaron sin errores. Esto significa que, también en el lado del servidor, [this.$store] hace referencia al almacén [Vuex]. La línea 31 aumentó el contador del almacén a 25;
  • una vez hecho esto, la página se envió al cliente;

Si observamos la página recibida por el cliente, encontramos los siguientes elementos:


<!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-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_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-02] : page serveur, page client</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link active nuxt-link-active">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/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 - value = 25
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
  window.__NUXT__ = (function (a, b, c) {
      return {
        layout: "default", data: [{}], error: null, state: { counter: 25 }, serverRendered: true,
        logs: [
          { date: new Date(1574085336802), args: ["[home beforeCreate]"], type: a, level: b, tag: c },
          { date: new Date(1574085336839), args: ["[home created]"], type: a, level: b, tag: c },
          { date: new Date(1574085336869), args: ["value=", "25"], type: a, level: b, tag: c }
        ]
      }
    }("log", 2, ""));</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • línea 47: el valor enviado por el servidor;
  • línea 60: se observa que el estado del almacén [Vuex] se ha incluido en la página. Esto permitirá al cliente, que se ejecutará tras recibir la página, reconstruir un nuevo almacén [Vuex] con 25 como valor inicial del contador;

Tras recibir y mostrar la página recibida del servidor, el cliente toma el control y ejecuta a su vez la página [page1]:


...

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page1 beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[page1 created]')
    // solo servidor
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page1 mounted]')
  }
}
</script>
  • línea 34: la propiedad [value] de la línea 18 recibe a su vez el valor 25 del contador;

El almacén de [nuxt] permite, por tanto, que el servidor transmita información al cliente durante la carga inicial de la página, cuando esta se solicita al servidor. Cabe recordar que, una vez obtenida esta página, ya no se solicita nada más al servidor y la aplicación funciona como una aplicación [vue] clásica, en modo SAP.

5.3. La página [page2]

En la página [page2], mostramos otra forma de conseguir que:

  • el servidor incluya información calculada en la página;
  • el cliente no la modifique;

5.3.1. El código de la página

El código de la página [page2] evoluciona de la siguiente manera:


<!-- página 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje -->
    <b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page2',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // ¿Quién ejecuta este código?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // la simulamos con una espera de un segundo
        setTimeout(() => {
          // este resultado se incluirá en las propiedades de [data]
          resolve({ value: 87 })
          // registro
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page2 beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[page2 created]')
  },
  beforeMount() {
    // solo cliente
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page2 mounted]')
  }
}
</script>
  • línea 7: la página muestra el valor de una propiedad denominada [value];
  • la propiedad [value] no existe como elemento de un objeto devuelto por la función [data]. En este caso, dicha función no existe. La propiedad [value] se crea dinámicamente en la línea 36;
  • línea 25: la función [asyncData] es una función [nuxt]. Como su nombre indica, normalmente se trata de una función asíncrona. Su función habitual es recuperar datos externos. [nuxt] garantiza que la página no se envíe al navegador del cliente hasta que la función [asyncData] haya devuelto sus datos asíncronos;
  • la función [asyncData] recibe como parámetro el contexto [nuxt]. Este objeto es muy completo y da acceso a mucha información sobre la aplicación [Nuxt]. Lo veremos en las secciones siguientes;
  • línea 31: se implementa la función [asyncData] con un [Promise] (véase el documento |Introducción al lenguaje ECMASCRIPT 6 con ejemplos|). El constructor de esta clase acepta como parámetro una función asíncrona que:
    • indica que se ha realizado correctamente devolviendo datos mediante la función [resolve]. El objeto devuelto por esta función se incluye automáticamente en las propiedades [data] de la página;
    • indica un error devolviendo un error mediante la función [reject];
  • línea 34: se simula una función asíncrona con la función [setTimeout]. Esta función devuelve el objeto [{ value: 87 }] (línea 36) al cabo de un segundo (línea 31) gracias a la función [resolve], que indica que la función [Promise] se ha ejecutado correctamente. El objeto devuelto por la función asíncrona se incluye automáticamente en las propiedades [data] de la página. Y es precisamente esta propiedad la que muestra la línea 7;
  • línea 27: veremos que la función [asyncData] se ejecuta en el servidor, pero no en el cliente;
  • línea 29: la inicialización de la propiedad [value] la realiza el servidor;

Nota: el objeto [this] no se reconoce en la función [asyncData], ya que aún no se ha creado el objeto que encapsula el componente [vue];

5.3.2. Ejecución

Se ejecuta el proyecto [nuxt-02] y se escribe [localhost:81/nuxt-02/page2] manualmente para que se solicite al servidor. Al igual que al inicio con la página [index]:

  • el servidor ejecuta la página [page2.vue];
  • envía la página generada al navegador. Esta se muestra;
  • los scripts de cliente integrados en la página enviada toman el control y vuelven a ejecutar la página [page2.vue];
  • la página mostrada se modifica entonces;

El resultado final es el siguiente:

Image

Esta vez, el valor mostrado es efectivamente el establecido por el servidor y, visualmente, no se aprecia que la página «titubee» debido a un cambio por parte del cliente del valor mostrado por el servidor. ¿Qué ha ocurrido esta vez?

El servidor ha ejecutado la siguiente página [page2]:


<!-- página 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje -->
    <b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page2',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // ¿Quién ejecuta este código?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // la simulamos con una espera de un segundo
        setTimeout(() => {
          // este resultado se incluirá en las propiedades de [data]
          resolve({ value: 87 })
          // registro
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page2 beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[page2 created]')
  },
  beforeMount() {
    // solo cliente
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page2 mounted]')
  }
}
</script>

La línea 36 es la que ha establecido el valor mostrado por la línea 7. Por lo tanto, eso es lo que ha recibido el navegador del cliente. Concretamente, recibe la siguiente página:


<!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-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_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-02] : page serveur, page client</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page2" target="_self" class="nav-link active nuxt-link-active">
                        Page 2
                      </a>
                    </li>
                  </ul>
                </div>
                <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-secondary">
                    Page 2 - value = 87
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
  window.__NUXT__ = (function (a, b, c) {
      return {
        layout: "default", data: [{ value: 87 }], error: null, state: { counter: 0 }, serverRendered: true,
        logs: [
          { date: new Date(1574096608555), args: ["asyncData, client=", "false", "serveur=", "true"], type: a, level: b, tag: c },
          { date: new Date(1574096608575), args: ["[page2 beforeCreate]"], type: a, level: b, tag: c },
          { date: new Date(1574096608599), args: ["[page2 created]"], type: a, level: b, tag: c }
        ]
      }
    }("log", 2, ""));</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • línea 48: se observa que el valor de la página recibida es 87;
  • línea 61: en la respuesta del servidor, se ven dos objetos: [data] y [state]:
    • [state] es el estado del almacén [Vuex]. Este se ha instanciado a partir del contenido de la carpeta [store] de la aplicación [nuxt-02];
    • [data] contiene las propiedades creadas por el servidor mediante la función [asyncData]. Encontramos la propiedad [value : 87] creada por el servidor. Los scripts del cliente integrarán esta propiedad en las de la página [page2];

Volvamos al código de la página [page2]:


<!-- página 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje -->
    <b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page2',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // ¿Quién ejecuta este código?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // la simulamos con una espera de un segundo
        setTimeout(() => {
          // este resultado se incluirá en las propiedades de [data]
          resolve({ value: 87 })
          // registro
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page2 beforeCreate]')
  },
  created() {
    // cliente y servidor
    console.log('[page2 created]')
  },
  beforeMount() {
    // solo cliente
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page2 mounted]')
  }
}
</script>
  • la línea 7 utiliza la propiedad [value]. Sin embargo, la página no define ninguna propiedad llamada [value]. No obstante, los scripts del cliente han creado automáticamente esta propiedad gracias al objeto [data: [{ value: 87 }]] recibido del servidor;

Además, los registros muestran que el cliente no ha ejecutado la función [asyncData]:

Image

La función [asyncData] fue ejecutada por el servidor [1], pero no por el cliente [2]. Por otra parte, cabe señalar que el servidor no ejecuta las funciones del ciclo de vida hasta que finaliza la función [asyncData]. Para comprobarlo, se puede aumentar el tiempo de espera dentro de la función [asyncData].

5.4. La página [page3]

Añadimos una nueva página [page3] a nuestra aplicación:

Image

5.4.1. El componente [navigation]

Se modifica el componente [vavigation] para permitir la navegación a la nueva página:


<template>
  <!-- menú 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-item to="/page3" exact exact-active-class="active">
      Page 3
    </b-nav-item>
  </b-nav>
</template>

5.4.2. El código de [page3]

El código de la página [page3] es el siguiente:


<!-- página 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje -->
    <b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page3',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // ¿Quién ejecuta este código?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // la simulamos con una espera de un segundo
        setTimeout(() => {
          // Éxito
          resolve()
        }, 1000)
      }).then(() => {
        // se modifica el store
        context.store.commit('increment', 28)
      })
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page3 beforeCreate]')
  },
  created() {
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page3 mounted]')
  }
}
</script>
  • línea 30: la función [fetch] tiene un comportamiento similar al de la función [asyncData]:
    • se ejecuta antes que las funciones del ciclo de vida;
    • el objeto [this] no se reconoce en esta función;
    • su funcionamiento es asíncrono;
    • el ciclo de vida no comienza hasta que la función asíncrona haya devuelto su resultado;
    • el resultado se devuelve aquí mediante el método [then] de [Promise], línea 43;
    • la función [fetch] recibe el parámetro [context]. Este representa el contexto [nuxt] del momento;
  • línea 30: entre sus numerosas propiedades, el objeto [context] tiene una propiedad [store] que representa el almacén [Vuex] de la aplicación;
  • línea 41: de forma artificial, se indica que la [Promise] se ha completado con éxito al cabo de un segundo (véase el documento |Introducción al lenguaje ECMASCRIPT 6 con ejemplos|);
  • línea 45: a continuación, se ejecuta el método [then]. En él se incrementa el contador de [store];

5.4.3. Ejecución

Se ejecuta el proyecto [nuxt-02] y se escribe [localhost:81/nuxt-02/page3] manualmente para que se solicite al servidor. Al igual que al inicio con la página [index]:

  • el servidor ejecuta la página [page3.vue];
  • envía la página generada al navegador. Esta se muestra;
  • los scripts de cliente integrados en la página enviada toman el control y vuelven a ejecutar la página [page3.vue];
  • la página mostrada se modifica entonces;

El resultado final es el siguiente:

Image

El valor mostrado es efectivamente el establecido por el servidor y, visualmente, no se aprecia que la página «titubee» debido a un cambio por parte del cliente del valor mostrado por el servidor. ¿Qué ha ocurrido esta vez?

El servidor ha ejecutado la siguiente página [page3]:


<!-- página 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje -->
    <b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page3',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // ¿Quién ejecuta este código?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // la simulamos con una espera de un segundo
        setTimeout(() => {
          // Éxito
          resolve()
        }, 1000)
      }).then(() => {
        // se modifica el store
        context.store.commit('increment', 28)
        // registro
        console.log('fetch commit terminé')
      })
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page3 beforeCreate]')
  },
  created() {
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page3 mounted]')
  }
}
</script>
  • línea 45: la función asíncrona [fetch] es la primera de las funciones anteriores en ejecutarse. Recibe como parámetro un objeto denominado [context], que es el contexto [nuxt] del momento. Entre las numerosas propiedades de este objeto, la propiedad [context.store] representa el almacén [Vuex];
  • línea 45: en la función asíncrona [fetch], el servidor establece el contador del almacén en 28;
  • línea 56: cuando se ejecuta la función [created], [nuxt] garantiza que la función asíncrona [fetch] haya finalizado su trabajo;
  • línea 58: el valor del contador de la persiana se asigna a la propiedad [value] de la línea 27;
  • línea 7: se muestra el valor de [value], es decir, el del contador del almacén;

El navegador del cliente recibe la siguiente página:


<!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-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_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-02] : page serveur, page client</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page2" target="_self" class="nav-link">
                        Page 2
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page3" target="_self" class="nav-link active nuxt-link-active">
                        Page 3
                      </a>
                    </li>
                  </ul>
                </div> <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-secondary">
                    Page 3 - value = 28
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
  window.__NUXT__ = (function (a, b, c) {
      return {
        layout: "default", data: [{}], error: null, state: { counter: 28 }, serverRendered: true,
        logs: [
          { date: new Date(1574169916025), args: ["fetch, client=", "false", "serveur=", "true"], type: a, level: b, tag: c },
          { date: new Date(1574169917038), args: ["fetch commit terminé"], type: a, level: b, tag: c },
          { date: new Date(1574169917137), args: ["[page3 beforeCreate]"], type: a, level: b, tag: c },
          { date: new Date(1574169917167), args: ["[page3 created], value=", "28"], type: a, level: b, tag: c }
        ]
      }
    }("log", 2, ""));</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • línea 52: se observa que el valor de la página recibida es 28;
  • línea 65: en la respuesta del servidor, se observa que el servidor ha enviado al cliente el estado [state] del almacén [Vuex]. Gracias a esta información, los scripts del cliente podrán reconstruir un almacén [Vuex];

A su vez, los scripts del cliente ejecutarán el código de la página [page3]:


<!-- página 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje -->
    <b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page3',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // ¿Quién ejecuta este código?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // la simulamos con una espera de un segundo
        setTimeout(() => {
          // Éxito
          resolve()
        }, 1000)
      }).then(() => {
        // se modifica el almacén
        context.store.commit('increment', 28)
        // registro
        console.log('fetch commit terminé')
      })
    }
  },
  // ciclo de vida
  beforeCreate() {
    // cliente y servidor
    console.log('[page3 beforeCreate]')
  },
  created() {
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page3 mounted]')
  }
}
</script>
  • línea 58: la función [created] ejecutada por el cliente asigna el valor del contador a la propiedad [value] de la línea 27;
  • la línea 7 muestra este valor. Dado que es el mismo que el enviado por el servidor, no se observa que la página «titubee» debido a un cambio;

Además, los registros muestran que el cliente no ha ejecutado la función [fetch]:

Image

La función [fetch] fue ejecutada por el servidor [1], pero no por el cliente [2]. Por otra parte, cabe señalar que el servidor no ejecuta las funciones del ciclo de vida hasta que finalice la función [fetch] [3]. Para comprobarlo, se puede aumentar el tiempo de espera dentro de la función [fetch].

Las páginas [page1] y [page3] han mostrado dos métodos que utilizan el almacén [Vuex] para transmitir información del servidor al cliente. Cabe preguntarse si son equivalentes. Creamos una página [page4] para comprobarlo.

5.5. La página [page4]

Añadimos una nueva página [page4] a nuestra aplicación:

Image

5.5.1. El componente [navigation]

Se modifica el componente [navigation] para permitir la navegación a la nueva página:


<template>
  <!-- menú Bootstrap con cinco 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-item to="/page3" exact exact-active-class="active">
      Page 3
    </b-nav-item>
    <b-nav-item to="/page4" exact exact-active-class="active">
      Page 4
    </b-nav-item>
  </b-nav>
</template>

5.5.2. El código de [page4]

El código de la página [page4] es el siguiente:


<!-- página 4 -->
<template>
  <Layout :left="true" :right="true">
    <!-- navegación -->
    <Navigation slot="left" />
    <!-- mensaje -->
    <b-alert slot="right" show variant="secondary"> Page 4 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page4',
  // componentes utilizados
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  async beforeCreate() {
    // cliente y servidor
    console.log('[page4 beforeCreate]')
    // solo para el servidor
    if (process.server) {
      // se ejecuta la función asíncrona
      const valeur = await new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // se simula con una espera de 10 segundos
        setTimeout(() => {
          // Éxito: se devuelve el valor del contador
          resolve(52)
        }, 10000)
      })
      // Se modifica el store
      this.$store.commit('increment', valeur)
      // registro
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
  beforeMount() {
    // solo cliente
    console.log('[page4 beforeMount]')
  },
  mounted() {
    // solo cliente
    console.log('[page4 mounted]')
  }
}
</script>
  • línea 30: lo que antes se hacía en la función [fetch] ahora se hace en el método [beforeCreate]. Se utiliza el par async (línea 30) / await (línea 36) para esperar a que finalice la función asíncrona;
  • línea 36: se recupera el resultado de la función asíncrona devuelto en la línea 41 tras 10 segundos (línea 42);
  • líneas 50-54: en el método [created], que se ejecuta tanto en el lado del servidor como en el del cliente, el contador se asigna a la propiedad [value] de la página;

5.5.3. Ejecución

Se ejecuta el proyecto [nuxt-02] y se introduce manualmente [localhost:81/nuxt-02/page4] para que se solicite al servidor. Al igual que al inicio con la página [index]:

  • el servidor ejecuta la página [page4.vue];
  • envía la página generada al navegador. Esta se muestra;
  • los scripts de cliente integrados en la página enviada toman el control y vuelven a ejecutar la página [page4.vue];
  • la página mostrada se modifica entonces;

El resultado final es el siguiente:

Image

Contrariamente a lo esperado, el valor que se muestra en [2] no es 52. ¿Qué ha pasado?

Los registros son los siguientes:

Image

Se puede observar que en [1] no se ha mostrado el registro de finalización de la acción asíncrona. La función [created], que muestra el valor del contador, muestra 0. Todo ello sugiere que [nuxt] no ha esperado a que finalizara la acción asíncrona.

Si volvemos al terminal de VSCode, que se utilizó para iniciar la aplicación, encontramos los registros de [3-4]. Se observa que la función asíncrona se ha ejecutado correctamente en el lado del servidor.

En definitiva, la función [beforeCreate] se ha ejecutado íntegramente en el servidor, pero [nuxt] no ha esperado a que finalizara su ejecución para enviar la página al navegador del cliente, mientras que sí espera a que finalice la función [fetch]. Por lo tanto, este es el método que hay que utilizar si se quiere que el servidor inicialice un almacén [Vuex].

5.6. Navegación por la aplicación [vue]

Hemos mostrado lo que ocurría cuando el servidor cargaba inicialmente cada una de las páginas [index, page1, page2, page3, page4]. En la práctica, esto no es lo que ocurre: en un funcionamiento normal, solo se busca en el servidor la página [index]. Veamos las tres páginas en este caso:

página [index]

Image

Ya hemos explicado este resultado en el apartado «enlace».

Ahora hagamos clic en el enlace [Page 1]:

Image

El valor que se muestra es 0. Era 25 cuando se solicitó la página por primera vez al servidor escribiendo manualmente su URL. La explicación es sencilla. El código ejecutado es el siguiente:


  created() {
    // cliente y servidor
    console.log('[page1 created]')
    // solo servidor
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('value=', this.value)
},

Es la línea 6 la que ponía el contador en 25. Como la página no se solicitó al servidor, las líneas 5-7 no se ejecutaron y el contador de la variable [Vuex] se quedó en 0.

Ahora, hagamos clic en el enlace [Page 2]:

Image

Esta vez no se muestra ningún valor y, además, aparece una advertencia en los registros de la consola:

Image

  • En [1], vemos que la función [asyncData] ha sido ejecutada por el cliente. Siempre ocurre lo mismo:
    • la ejecuta el servidor si la página se solicita al servidor; en ese caso, no la ejecuta el cliente;
    • y cada vez que la página es el destino de la ruta actual del cliente;
  • en [2]: [nuxt] emite una advertencia porque en la plantilla de la página hay una expresión reactiva {{ value }}, mientras que la página no tiene la propiedad [value];

Recordemos el código ejecutado por el cliente:


asyncData() {
    // ¿Quién ejecuta este código?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente aquí habría una función asíncrona, pero en este caso no
        // este resultado se incluirá en las propiedades de [data]
        resolve({ value: 87 })
      })
    }
  },
  • en la línea 3 se ha podido comprobar que la función [asyncData] fue ejecutada por el cliente incluso antes que las funciones del ciclo de vida;
  • la línea 5 impidió la ejecución del resto del código que creaba la propiedad [value], ya que dicho código lo ejecuta el cliente;

Ahora pasemos a la página [page3]:

Image

  • en [2], teníamos 28 cuando la página fue servida por el servidor. Aquí no es así;
  • en [4], vemos que la función [fetch] se ha ejecutado en el lado del cliente;

Analicemos el código ejecutado por el cliente:


...
fetch(context) {
    // ¿Quién ejecuta este código?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // solo para el servidor
    if (process.server) {
      // se devuelve una promesa
      return new Promise(function(resolve, reject) {
        // Normalmente, aquí hay una función asíncrona
        // la simulamos con una espera de un segundo
        setTimeout(() => {
          // Éxito
          resolve()
        }, 1000)
      }).then(() => {
        // se modifica el store
        context.store.commit('increment', 28)
        // registro
        console.log('fetch commit terminé')
      })
    }
},
...
  • el cliente ejecuta el método [fetch], línea 2;
  • las líneas 6-21 no se ejecutan porque la condición [process.server] es falsa. Por lo tanto, la línea 17, que establece el contador en 28, no se ejecuta. Se queda en cero. Por eso el cliente muestra 0 en lugar de 28;

Pasemos ahora a la página [page4]. Obtenemos el siguiente resultado:

Image

  • en [2], el valor del contador;
  • en [3], los registros del cliente;

El código ejecutado por el cliente es el siguiente:


...
data() {
    return {
      value: 0
    }
  },
  // ciclo de vida
  async beforeCreate() {
    // cliente y servidor
    console.log('[page4 beforeCreate]')
    // solo para el servidor
    if (process.server) {
      // se ejecuta la función asíncrona
      const valeur = await new Promise(function(resolve, reject) {
        // Aquí normalmente hay una función asíncrona
        // se simula con una espera de 10 segundos
        setTimeout(() => {
          // Éxito: se devuelve el valor del contador
          resolve(52)
        }, 10000)
      })
      // Se modifica el store
      this.$store.commit('increment', valeur)
      // registro
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // cliente y servidor
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
...
  • las líneas 12-26 no son ejecutadas por el cliente, ya que la condición [process.server] de la línea 12 es falsa. Por lo tanto, el contador del almacén [Vuex] tiene un valor de 0 (su valor inicial en el almacén) y es este valor el que muestra la página;

Cabe preguntarse qué ocurre cuando se comenta la condición [if] de las líneas 12 y 26. Esta es la respuesta:

  • esta vez, el cliente ejecuta las líneas 14-25, pero [nuxt] no espera a que finalice la función asíncrona (como ocurre con el servidor) y, por lo tanto, deja el contador en 0;
  • al cabo de 10 s, la función asíncrona finaliza y el contador se establece en 52 en la línea 23;
  • cuando se vuelve a acceder a la página [page4], se muestra el valor 52;

5.7. Résumé

De nuestras diferentes pruebas, cabe destacar los siguientes puntos:

  • si la página [index] debe contener datos externos, el servidor puede recuperarlos mediante la función [asyncData];
  • si el servidor debe inicializar un almacén [Vuex] con datos externos al cargar la página [index], lo hará en la función [fetch];
  • la página generada por el servidor y la generada por el cliente deben ser idénticas si se quiere evitar el efecto de «salto» provocado por el hecho de que la página del cliente sustituya visualmente a la página enviada por el servidor y mostrada inicialmente;

Vamos a descubrir otros aspectos de [nuxt] a través de un nuevo ejemplo.