5. Esempio [nuxt-02]: Pagine server e client
In questo progetto, dimostriamo:
- che la pagina creata dal client può apparire diversa da quella ricevuta dal server. Ciò comporta un rapido cambiamento di pagina che l'utente nota, il che è dannoso per l'usabilità dell'app. È quindi un'opzione da evitare;
- una soluzione per la pagina lato client per ricreare la stessa pagina inviata dal server;
Il progetto [nuxt-02] viene inizialmente creato clonando il progetto [nuxt-01].

Al progetto viene aggiunta una cartella [store], insieme a due nuove pagine. Ci torneremo più avanti.
5.1. La pagina [index]
5.1.1. Il codice della pagina
Il codice della pagina [index] diventa il seguente:
<!-- page principale -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message-->
<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',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[home beforeCreate]')
},
created() {
// client and server
console.log('[home created]')
// server only
if (process.server) {
this.value = 10
}
// client and server
console.log('value=', this.value)
},
beforeMount() {
// customer only
console.log('[home beforeMount]')
},
mounted() {
// customer only
console.log('[home mounted]')
}
}
</script>
Commenti
- riga 7: la pagina [index] visualizzerà il valore della sua proprietà [value] (riga 28);
- righe 36–45: è importante ricordare qui che la funzione [created] viene eseguita sia sul server che sul client. Righe 40–42: il server imposterà il valore della proprietà [value] a 10. Il client, tuttavia, non modifica questo valore. Vogliamo semplicemente sapere se questo valore viene mantenuto dal client. Scopriremo che non è così;
5.1.2. Esecuzione
Modifichiamo il file [/nuxt.config.js] per eseguire il progetto [nuxt-02]:
...
// source code directory
srcDir: 'nuxt-02',
// router
router: {
// application URL root
base: '/nuxt-02/'
},
// server
server: {
// service port, default 3000
port: 81,
// network addresses listened to, default localhost: 127.0.0.1
// 0.0.0.0 = all the machine's network addresses
host: 'localhost'
}
...
Eseguiamo il progetto [1]:

Viene quindi visualizzata la pagina [index] [2-3]. Mostra il valore [10] per qualche istante e poi visualizza il valore [0]. Cosa è successo?
Passaggio 1
Il server si avvia per primo. Esegue il codice nella pagina [index]:
export default {
name: 'Home',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[home beforeCreate]')
},
created() {
// client and server
console.log('[home created]')
// server only
if (process.server) {
this.value = 10
}
// client and server
console.log('value=', this.value)
},
beforeMount() {
// customer only
console.log('[home beforeMount]')
},
mounted() {
// customer only
console.log('[home mounted]')
}
}
- A causa della riga 23, la proprietà [value] alla riga 10 assume il valore 10;
È possibile verificarlo controllando il codice sorgente della pagina visualizzata dal browser (tramite l'opzione [Visualizza sorgente] del browser):
<!doctype html>
<html data-n-head-ssr>
<head>
<title>Introduction à [nuxt.js]</title>
<meta data-n-head="ssr" charset="utf-8">
<meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
<meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
<link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
<base href="/nuxt-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>
- riga 46: nella pagina ricevuta, [value] aveva il valore 10;
Passaggio 2
Sappiamo che dopo la ricezione della pagina, gli script alle righe 57–60 subentrano e modificano il comportamento della pagina ricevuta, comprese le informazioni visualizzate, come mostrato qui. Questi script costituiscono il client, che esegue anche il codice della pagina [index] — lo stesso codice del server:
export default {
name: 'Home',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[home beforeCreate]')
},
created() {
// client and server
console.log('[home created]')
// server only
if (process.server) {
this.value = 10
}
// client and server
console.log('value=', this.value)
},
beforeMount() {
// customer only
console.log('[home beforeMount]')
},
mounted() {
// customer only
console.log('[home mounted]')
}
}
- Per capire cosa sta succedendo, devi tenere presente che il client [nuxt] non eseguirà le righe 22–24 (process.server=false);
- in una classica applicazione [Vue], la proprietà [value] alla riga 10 rimane a 0. Ecco perché, una volta che il client ha effettuato la navigazione verso la pagina ricevuta, il valore visualizzato diventa [0];
Il valore generato dal server [nuxt] per la proprietà [value] era inutile.
5.2. La pagina [page1]
5.2.1. Lo store [Vuex]
Abbiamo aggiunto una cartella [store] al progetto [nuxt-02]:

La presenza di questa cartella fa sì che [nuxt] implementi automaticamente uno store [Vuex]. Il file [index.js] implementa questo store. Di seguito è riportato il contenuto del file [index.js]:
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state, inc) {
state.counter += inc
}
}
[nuxt] implementa uno store [Vuex] basato sul contenuto di [index.js]:
- righe 1–3: definizione dello [state] dello store. Questo stato viene restituito da una funzione. Qui, lo stato ha una sola proprietà, il contatore alla riga 2. La funzione esportata deve essere denominata [state];
- righe 5-9: le operazioni possibili sullo stato dello store. Queste sono chiamate [mutazioni]. Qui, la mutazione [increment] incrementa la proprietà [contatore] di un valore [inc]. L'oggetto esportato deve essere denominato [mutazioni];
Lo [store] Vuex implementato da [nuxt] è disponibile in vari punti. Nelle viste, è disponibile nella proprietà [this.$store].
5.2.2. Il codice della pagina
Come la pagina [index], la pagina [page1] visualizzerà un valore: il contatore dallo store Vuex:
<!-- page 1 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message-->
<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',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[home beforeCreate]')
},
created() {
// client and server
console.log('[home created]')
// server only
if (process.server) {
this.$store.commit('increment', 25)
}
// client and server
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// customer only
console.log('[home beforeMount]')
},
mounted() {
// customer only
console.log('[home mounted]')
}
}
</script>
Commenti
- righe 38–40: il server incrementerà il contatore di 25;
- riga 42: sia il server che il client visualizzeranno il valore del contatore;
- riga 7: viene visualizzato il valore del contatore;
Quando si legge questo codice, è necessario comprendere due cose:
- il codice eseguito è lo stesso sia per il server che per il client;
- l'oggetto [this] non è lo stesso: esiste un [this] lato server e un [this] lato client;
Vogliamo sapere se il [this.$store] del server è uguale al [this.$store] del client. Dato che il server viene eseguito per primo (all'avvio dell'applicazione), la domanda si riduce a: lo [store] inizializzato dal server viene passato al client?
5.2.3. Esecuzione
Eseguiamo il progetto [nuxt-02] e digitiamo manualmente [localhost:81/nuxt-02/page1] per avviare il server. Come per la pagina [index] all'avvio:
- il server esegue la pagina [page1.vue];
- invia la pagina generata al browser. La pagina viene visualizzata;
- gli script lato client incorporati nella pagina inviata prendono il controllo ed eseguono nuovamente la pagina [page1.vue];
- la pagina visualizzata viene quindi modificata;
Il risultato finale è il seguente:

Questa volta, il valore visualizzato è effettivamente quello impostato dal server e, visivamente, la pagina non "salta" a causa di una modifica lato client al valore visualizzato dal server. Cosa è successo questa volta?
Il server ha eseguito la seguente pagina [page1]:
...
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page1 beforeCreate]')
},
created() {
// client and server
console.log('[page1 created]')
// server only
if (process.server) {
this.$store.commit('increment', 25)
}
// client and server
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// customer only
console.log('[page1 beforeMount]')
},
mounted() {
// customer only
console.log('[page1 mounted]')
}
}
</script>
- Le righe 30–32 sono state eseguite senza errori. Ciò significa che anche sul lato server [this.$store] fa riferimento allo store [Vuex]. La riga 31 ha impostato il contatore dello store su 25;
- dopodiché la pagina è stata inviata al client;
Se osserviamo la pagina ricevuta dal client, troviamo i seguenti elementi:
<!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>
- riga 47: il valore inviato dal server;
- riga 60: vediamo che lo stato dello store [Vuex] è stato incorporato nella pagina. Ciò consentirà al client, che verrà eseguito dopo aver ricevuto la pagina, di ricostruire un nuovo store [Vuex] con 25 come valore iniziale del contatore;
Dopo aver ricevuto e visualizzato la pagina dal server, il client prende il controllo ed esegue a sua volta la pagina [page1]:
...
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page1 beforeCreate]')
},
created() {
// client and server
console.log('[page1 created]')
// server only
if (process.server) {
this.$store.commit('increment', 25)
}
// client and server
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// customer only
console.log('[page1 beforeMount]')
},
mounted() {
// customer only
console.log('[page1 mounted]')
}
}
</script>
- riga 34: alla proprietà [value] della riga 18 viene assegnato il valore 25 proveniente dal contatore;
Lo store [nuxt] consente quindi al server di trasmettere informazioni al client durante il caricamento iniziale della pagina, quando la pagina viene richiesta al server. Si noti che una volta ottenuta questa pagina, il server non è più coinvolto e l'applicazione funziona come una classica applicazione [vue], in modalità a pagina singola.
5.3. La pagina [page2]
Nella pagina [page2], mostriamo un altro modo per:
- il server includa informazioni calcolate nella pagina;
- il client non modifica queste informazioni;
5.3.1. Il codice della pagina
Il codice della pagina [page2] cambia come segue:
<!-- page2 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message -->
<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',
// components used
components: {
Layout,
Navigation
},
asyncData(context) {
// who executes this code?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// this result will be included in the properties of [data]
resolve({ value: 87 })
// log
console.log('asynData terminée')
}, 1000)
})
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page2 beforeCreate]')
},
created() {
// client and server
console.log('[page2 created]')
},
beforeMount() {
// customer only
console.log('[page2 beforeMount]')
},
mounted() {
// customer only
console.log('[page2 mounted]')
}
}
</script>
- riga 7: la pagina visualizza il valore di una proprietà denominata [value];
- la proprietà [value] non esiste come parte di un oggetto restituito dalla funzione [data]. Qui, questa funzione non esiste. La proprietà [value] viene creata dinamicamente dalla riga 36;
- riga 25: la funzione [asyncData] è una funzione [nuxt]. Come suggerisce il nome, si tratta di una funzione asincrona. Il suo ruolo principale è quello di recuperare dati esterni. [nuxt] garantisce che la pagina non venga inviata al browser del cliente finché la funzione [asyncData] non ha completato il rendering dei dati asincroni;
- La funzione [asyncData] riceve il contesto [nuxt] come parametro. Questo oggetto è molto ricco e fornisce accesso a molte informazioni sull'applicazione [nuxt]. Lo esploreremo nelle sezioni seguenti;
- Riga 31: Implementiamo la funzione [asyncData] utilizzando una [Promise] (vedi il documento |Introduzione a ECMAScript 6 con esempi|). Il costruttore di questa classe accetta come parametro una funzione asincrona che:
- indica il successo restituendo i dati con la funzione [resolve]. L'oggetto restituito da questa funzione viene automaticamente incluso nelle proprietà [data] della pagina;
- segnala un errore restituendo un errore tramite la funzione [reject];
- riga 34: simuliamo una funzione asincrona utilizzando la funzione [setTimeout]. Questa funzione restituisce l'oggetto [{ value: 87 }] (riga 36) dopo un secondo (riga 31) utilizzando la funzione [resolve], che segnala che la [Promise] ha avuto esito positivo. L'oggetto restituito dalla funzione asincrona viene automaticamente incluso nelle proprietà [data] della pagina. Ed è proprio questa proprietà che viene visualizzata alla riga 7;
- riga 27: vedremo che la funzione [asyncData] viene eseguita dal server ma non dal client;
- riga 29: la proprietà [value] viene inizializzata dal server;
Nota: l'oggetto [this] non viene riconosciuto nella funzione [asyncData] perché l'oggetto che incapsula il componente [vue] non è ancora stato creato;
5.3.2. Esecuzione
Eseguiamo il progetto [nuxt-02] e digitiamo manualmente [localhost:81/nuxt-02/page2] per avviare il server. Come per il lancio iniziale della pagina [index]:
- il server esegue la pagina [page2.vue];
- invia la pagina generata al browser. La pagina viene visualizzata;
- gli script lato client incorporati nella pagina inviata prendono il controllo ed eseguono nuovamente la pagina [page2.vue];
- la pagina visualizzata viene quindi modificata;
Il risultato finale è il seguente:

Questa volta, il valore visualizzato è effettivamente quello impostato dal server e, visivamente, la pagina non "salta" a causa di una modifica lato client al valore visualizzato dal server. Cosa è successo questa volta?
Il server ha eseguito la seguente pagina [page2]:
<!-- page2 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message -->
<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',
// components used
components: {
Layout,
Navigation
},
asyncData(context) {
// who executes this code?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// this result will be included in the properties of [data]
resolve({ value: 87 })
// log
console.log('asynData terminée')
}, 1000)
})
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page2 beforeCreate]')
},
created() {
// client and server
console.log('[page2 created]')
},
beforeMount() {
// customer only
console.log('[page2 beforeMount]')
},
mounted() {
// customer only
console.log('[page2 mounted]')
}
}
</script>
La riga 36 imposta il valore visualizzato dalla riga 7. Questo è ciò che ha ricevuto il browser del client. Nello specifico, ha ricevuto la seguente pagina:
<!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>
- riga 48: vediamo che il valore nella pagina ricevuta è 87;
- riga 61: nella risposta del server, vediamo due oggetti: [data] e [state]:
- [state] è lo stato dello store [Vuex]. Questo è stato istanziato dal contenuto della cartella [store] nell'applicazione [nuxt-02];
- [data] contiene le proprietà create dal server utilizzando la funzione [asyncData]. Troviamo la proprietà [value: 87] creata dal server. Gli script lato client incorporeranno questa proprietà in quelle della pagina [page2];
Torniamo al codice della pagina [page2]:
<!-- page2 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message -->
<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',
// components used
components: {
Layout,
Navigation
},
asyncData(context) {
// who executes this code?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// this result will be included in the properties of [data]
resolve({ value: 87 })
// log
console.log('asynData terminée')
}, 1000)
})
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page2 beforeCreate]')
},
created() {
// client and server
console.log('[page2 created]')
},
beforeMount() {
// customer only
console.log('[page2 beforeMount]')
},
mounted() {
// customer only
console.log('[page2 mounted]')
}
}
</script>
- La riga 7 utilizza la proprietà [value]. Tuttavia, la pagina non definisce alcuna proprietà denominata [value]. Gli script lato client hanno creato automaticamente questa proprietà utilizzando l'oggetto [data: [{ value: 87 }]] ricevuto dal server;
I log mostrano inoltre che la funzione [asyncData] non è stata eseguita dal client:

La funzione [asyncData] è stata eseguita dal server [1] ma non dal client [2]. Inoltre, va notato che le funzioni del ciclo di vita non vengono eseguite dal server finché la funzione [asyncData] non è terminata. Possiamo aumentare il tempo di attesa all'interno della funzione [asyncData] per verificarlo.
5.4. La pagina [page3]
Stiamo aggiungendo una nuova pagina [page3] alla nostra applicazione:

5.4.1. Il componente [navigation]
Il componente [navigation] è stato modificato per consentire la navigazione verso la nuova pagina:
<template>
<!-- bootstrap menu with three options -->
<b-nav vertical>
<b-nav-item to="/" exact exact-active-class="active">
Home
</b-nav-item>
<b-nav-item to="/page1" exact exact-active-class="active">
Page 1
</b-nav-item>
<b-nav-item to="/page2" exact exact-active-class="active">
Page 2
</b-nav-item>
<b-nav-item to="/page3" exact exact-active-class="active">
Page 3
</b-nav-item>
</b-nav>
</template>
5.4.2. Il codice per [pagina3]
Il codice per la pagina [page3] è il seguente:
<!-- page3 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message -->
<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',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// who executes this code?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// success
resolve()
}, 1000)
}).then(() => {
// modify the blind
context.store.commit('increment', 28)
})
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page3 beforeCreate]')
},
created() {
// client and server
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// customer only
console.log('[page3 beforeMount]')
},
mounted() {
// customer only
console.log('[page3 mounted]')
}
}
</script>
- Riga 30: La funzione [fetch] si comporta in modo simile alla funzione [asyncData]:
- viene eseguita prima delle funzioni del ciclo di vita;
- l'oggetto [this] non viene riconosciuto in questa funzione;
- opera in modo asincrono;
- il ciclo di vita non inizia finché la funzione asincrona non ha restituito il suo risultato;
- il risultato viene restituito qui dal metodo [then] di [Promise], riga 43;
- La funzione [fetch] riceve il parametro [context]. Questo rappresenta il contesto [nuxt] corrente;
- riga 30: tra le sue numerose proprietà, l'oggetto [context] ha una proprietà [store] che rappresenta lo store [Vuex] dell'applicazione;
- riga 41: artificialmente, segnaliamo il successo della [Promise] dopo un secondo (vedi documento |Introduzione a ECMAScript 6 attraverso esempi|);
- riga 45: viene quindi eseguito il metodo [then]. Qui, il contatore [store] viene incrementato;
5.4.3. Esecuzione
Eseguiamo il progetto [nuxt-02] e digitiamo manualmente [localhost:81/nuxt-02/page3] per avviare il server. Come per la pagina [index] all'avvio:
- il server esegue la pagina [page3.vue];
- invia la pagina generata al browser. La pagina viene visualizzata;
- gli script lato client incorporati nella pagina inviata prendono il controllo ed eseguono nuovamente la pagina [page3.vue];
- la pagina visualizzata viene quindi modificata;
Il risultato finale è il seguente:

Il valore visualizzato è effettivamente quello impostato dal server e, visivamente, la pagina non "salta" a causa di una modifica lato client al valore visualizzato dal server. Cosa è successo questa volta?
Il server ha eseguito la seguente pagina [page3]:
<!-- page3 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message -->
<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',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// who executes this code?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// success
resolve()
}, 1000)
}).then(() => {
// modify the blind
context.store.commit('increment', 28)
// log
console.log('fetch commit terminé')
})
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page3 beforeCreate]')
},
created() {
// client and server
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// customer only
console.log('[page3 beforeMount]')
},
mounted() {
// customer only
console.log('[page3 mounted]')
}
}
</script>
- riga 45: la funzione asincrona [fetch] è la prima delle funzioni sopra indicate ad essere eseguita. Riceve come parametro un oggetto chiamato [context], che è il contesto [Nuxt] corrente. Tra le numerose proprietà di questo oggetto, la proprietà [context.store] rappresenta lo store [Vuex];
- Riga 45: nella funzione asincrona [fetch], il server imposta il contatore dello store su 28;
- riga 56: quando viene eseguita la funzione [created], [nuxt] si assicura che la funzione asincrona [fetch] abbia terminato il suo lavoro;
- riga 58: il valore del contatore dello store viene assegnato alla proprietà [value] alla riga 27;
- riga 7: visualizza il valore di [value], ovvero il contatore dello store;
Il browser client riceve la seguente pagina:
<!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>
- riga 52: vediamo che il valore nella pagina ricevuta è 28;
- riga 65: nella risposta del server, vediamo che il server ha inviato lo stato dello store [Vuex] al client. Utilizzando queste informazioni, gli script del client saranno in grado di ricostruire uno store [Vuex];
Gli script client a loro volta eseguiranno il codice per la pagina [page3]:
<!-- page3 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message -->
<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',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// who executes this code?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// success
resolve()
}, 1000)
}).then(() => {
// modify the blind
context.store.commit('increment', 28)
// log
console.log('fetch commit terminé')
})
}
},
// life cycle
beforeCreate() {
// client and server
console.log('[page3 beforeCreate]')
},
created() {
// client and server
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// customer only
console.log('[page3 beforeMount]')
},
mounted() {
// customer only
console.log('[page3 mounted]')
}
}
</script>
- riga 58: la funzione [created] eseguita dal client imposta il valore del contatore nella proprietà [value] alla riga 27;
- la riga 7 visualizza questo valore. Poiché è lo stesso di quello inviato dal server, non vediamo la pagina "saltare" a causa di una modifica;
I log mostrano inoltre che la funzione [fetch] non è stata eseguita dal client:

La funzione [fetch] è stata eseguita dal server [1] ma non dal client [2]. Inoltre, si noti che le funzioni del ciclo di vita non vengono eseguite dal server finché la funzione [fetch] non è terminata [3]. Possiamo aumentare il tempo di attesa all'interno della funzione [fetch] per verificarlo.
Le pagine [page1] e [page3] hanno illustrato due metodi che utilizzano lo store [Vuex] per trasmettere informazioni dal server al client. Ci si potrebbe chiedere se siano equivalenti. Creeremo una pagina [page4] per verificarlo.
5.5. La pagina [page4]
Stiamo aggiungendo una nuova pagina [page4] alla nostra applicazione:

5.5.1. Il componente [navigation]
Il componente [navigation] viene modificato per consentire la navigazione verso la nuova pagina:
<template>
<!-- bootstrap menu with five options -->
<b-nav vertical>
<b-nav-item to="/" exact exact-active-class="active">
Home
</b-nav-item>
<b-nav-item to="/page1" exact exact-active-class="active">
Page 1
</b-nav-item>
<b-nav-item to="/page2" exact exact-active-class="active">
Page 2
</b-nav-item>
<b-nav-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. Il codice per [pagina4]
Il codice per la pagina [page4] è il seguente:
<!-- page4 -->
<template>
<Layout :left="true" :right="true">
<!-- navigation -->
<Navigation slot="left" />
<!-- message -->
<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',
// components used
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// life cycle
async beforeCreate() {
// client and server
console.log('[page4 beforeCreate]')
// only for the server
if (process.server) {
// execute the asynchronous function
const valeur = await new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a 10-second wait
setTimeout(() => {
// success - the counter value is returned
resolve(52)
}, 10000)
})
// modify the blind
this.$store.commit('increment', valeur)
// log
console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
}
},
created() {
// client and server
this.value = this.$store.state.counter
console.log('[page4 created], value=', this.value)
},
beforeMount() {
// customer only
console.log('[page4 beforeMount]')
},
mounted() {
// customer only
console.log('[page4 mounted]')
}
}
</script>
- riga 30: ciò che prima veniva fatto nella funzione [fetch] ora viene fatto nel metodo [beforeCreate]. Usiamo la coppia async (riga 30) / await (riga 36) per attendere il completamento della funzione asincrona;
- riga 36: recuperiamo il risultato della funzione asincrona restituito alla riga 41 dopo 10 secondi (riga 42);
- righe 50–54: nel metodo [created], che viene eseguito sia sul server che sul client, il contatore viene assegnato alla proprietà [value] della pagina;
5.5.3. Esecuzione
Esegui il progetto [nuxt-02] e digita manualmente [localhost:81/nuxt-02/page4] per avviare il server. Come per il lancio iniziale della pagina [index]:
- il server esegue la pagina [page4.vue];
- invia la pagina generata al browser. La pagina viene visualizzata;
- gli script lato client incorporati nella pagina inviata prendono il controllo ed eseguono nuovamente la pagina [page4.vue];
- la pagina visualizzata viene quindi modificata;
Il risultato finale è il seguente:

Contrariamente alle aspettative, il valore visualizzato in [2] non è 52. Cosa è successo?
I log sono i seguenti:

Possiamo notare che in [1] non è stato visualizzato il log che indica la fine dell'azione asincrona. La funzione [created], che visualizza il valore del contatore, mostra 0. Tutto ciò suggerisce che [nuxt] non abbia atteso il completamento dell'azione asincrona.
Se torniamo al terminale di VSCode utilizzato per avviare l'applicazione, troviamo i log [3-4]. Possiamo vedere che la funzione asincrona è stata effettivamente eseguita sul lato server.
In definitiva, la funzione [beforeCreate] è stata effettivamente eseguita interamente sul lato server, ma [Nuxt] non ha atteso il completamento della sua esecuzione prima di inviare la pagina al browser del client, anche se attende il completamento della funzione [fetch]. Pertanto, questo è il metodo da utilizzare se si desidera che il server inizializzi uno store [Vuex].
5.6. Navigazione nell'applicazione [Vue]
Abbiamo mostrato cosa succede quando ciascuna delle pagine [index, page1, page2, page3, page4] viene caricata inizialmente dal server. In pratica, non è questo ciò che accade: in condizioni di funzionamento normale, solo la pagina [index] viene recuperata dal server. Diamo un'occhiata alle tre pagine in questo caso:
pagina [index]

Abbiamo già spiegato questo risultato nella sezione "link".
Ora clicchiamo sul link [Pagina 1]:

Il valore visualizzato è 0. Era 25 quando la pagina è stata richiesta per la prima volta al server digitando manualmente il suo URL. La spiegazione è semplice. Il codice eseguito è il seguente:
created() {
// client and server
console.log('[page1 created]')
// server only
if (process.server) {
this.$store.commit('increment', 25)
}
// client and server
this.value = this.$store.state.counter
console.log('value=', this.value)
},
La riga 6 era quella che impostava il contatore su 25. Poiché la pagina non era stata richiesta al server, le righe 5-7 non sono state eseguite e il contatore nello store [Vuex] è rimasto a 0.
Ora, clicchiamo sul link [Pagina 2]:

Questa volta non viene visualizzato alcun valore e vediamo anche un avviso nei log della console:

- In [1], vediamo che la funzione [asyncData] è stata eseguita dal client. Questo avviene sempre:
- viene eseguita dal server se la pagina viene richiesta dal server. In questo caso, non viene eseguita dal client;
- quindi ogni volta che la pagina è la destinazione del percorso corrente del client;
- in [2]: [nuxt] emette un avviso perché il template della pagina contiene un'espressione reattiva {{ value }}, anche se la pagina non ha la proprietà [value];
Rivediamo il codice eseguito dal client:
asyncData() {
// who executes this code?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function - so not here
// this result will be included in the properties of [data]
resolve({ value: 87 })
})
}
},
- La riga 3 mostrava che la funzione [asyncData] veniva eseguita dal client prima ancora delle funzioni del ciclo di vita;
- La riga 5 ha impedito l'esecuzione del resto del codice che creava la proprietà [value] poiché tale codice viene eseguito dal client;
Passiamo ora alla pagina [page3]:

- in [2], avevamo 28 quando la pagina è stata servita dal server. Qui non è così;
- In [4], vediamo che la funzione [fetch] è stata eseguita sul lato client;
Esaminiamo il codice eseguito dal client:
...
fetch(context) {
// who executes this code?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// only for the server
if (process.server) {
// we return a promise
return new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a one-second wait
setTimeout(() => {
// success
resolve()
}, 1000)
}).then(() => {
// modify the blind
context.store.commit('increment', 28)
// log
console.log('fetch commit terminé')
})
}
},
...
- Il client esegue il metodo [fetch], riga 2;
- le righe 6–21 non vengono eseguite perché la condizione [process.server] è falsa. Pertanto, la riga 17, che imposta il contatore a 28, non viene eseguita. Rimane a zero. Questo è il motivo per cui il client visualizza 0 invece di 28;
Passiamo ora alla pagina [page4]. Otteniamo il seguente risultato:

- in [2], il valore del contatore;
- in [3], i log del client;
Il codice eseguito dal client è il seguente:
...
data() {
return {
value: 0
}
},
// life cycle
async beforeCreate() {
// client and server
console.log('[page4 beforeCreate]')
// only for the server
if (process.server) {
// execute the asynchronous function
const valeur = await new Promise(function(resolve, reject) {
// this is normally an asynchronous function
// we simulate it with a 10-second wait
setTimeout(() => {
// success - the counter value is returned
resolve(52)
}, 10000)
})
// modify the blind
this.$store.commit('increment', valeur)
// log
console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
}
},
created() {
// client and server
this.value = this.$store.state.counter
console.log('[page4 created], value=', this.value)
},
...
- Le righe 12–26 non vengono eseguite dal client perché la condizione [process.server] alla riga 12 è falsa. Pertanto, il contatore nello store [Vuex] è 0 (il suo valore iniziale nello store) e questo è il valore visualizzato sulla pagina;
Potresti chiederti cosa succede quando commenti le istruzioni [if] alle righe 12 e 26. Ecco la risposta:
- Questa volta, il client esegue le righe 14–25, ma [nuxt] non attende il completamento della funzione asincrona (come fa sul server) e quindi lascia il contatore a 0;
- Dopo 10 secondi, la funzione asincrona si completa e il contatore viene impostato a 52 alla riga 23;
- quando si torna alla pagina [page4], viene visualizzato il valore 52;
5.7. Riepilogo
Dai nostri vari test, possiamo concludere quanto segue:
- se la pagina [index] deve contenere dati esterni, questi possono essere recuperati dal server utilizzando la funzione [asyncData];
- se il server deve inizializzare uno store [Vuex] con dati esterni durante il caricamento della pagina [index], lo farà nella funzione [fetch];
- la pagina generata dal server e quella generata dal client devono essere identiche per evitare l'effetto "jittering" causato dalla sostituzione visiva, da parte della pagina client-side, della pagina inviata dal server e inizialmente visualizzata;
Esploreremo altri aspetti di [Nuxt] attraverso un nuovo esempio.