5. Exemplo [nuxt-02]: Páginas do servidor e do cliente
Neste projeto, demonstramos:
- que a página criada pelo cliente pode ter um aspeto diferente daquela recebida do servidor. Isto resulta numa mudança rápida de página que o utilizador nota, o que é prejudicial para a usabilidade da aplicação. É, portanto, uma opção a evitar;
- uma solução para que a página do lado do cliente recrie a mesma página que a enviada pelo servidor;
O projeto [nuxt-02] é inicialmente criado através da clonagem do projeto [nuxt-01].

É adicionada uma pasta [store] ao projeto, juntamente com duas novas páginas. Voltaremos a este assunto.
5.1. A página [index]
5.1.1. O código da página
O código da página [index] fica assim:
<!-- 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>
Comentários
- linha 7: a página [index] exibirá o valor da sua propriedade [value] (linha 28);
- linhas 36–45: é importante lembrar aqui que a função [created] é executada tanto no servidor como no cliente. Linhas 40–42: o servidor definirá o valor da propriedade [value] como 10. O cliente, no entanto, não modifica este valor. Queremos simplesmente saber se este valor é retido pelo cliente. Vamos descobrir que não é;
5.1.2. Execução
Modificamos o ficheiro [/nuxt.config.js] para executar o projeto [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'
}
...
Executamos o projeto [1]:

A página [índice] é então apresentada [2-3]. Apresenta o valor [10] durante alguns instantes e, em seguida, apresenta o valor [0]. O que aconteceu?
Passo 1
O servidor é executado primeiro. Ele executa o código na página [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]')
}
}
- Devido à linha 23, a propriedade [value] na linha 10 assume o valor 10;
Pode verificar isso consultando o código-fonte da página recebida pelo navegador (a opção [ver código-fonte] no 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>
- linha 46: na página recebida, [value] tinha o valor 10;
Passo 2
Sabemos que, após a página ser recebida, os scripts nas linhas 57–60 assumem o controlo e transformam o comportamento da página recebida, incluindo as informações apresentadas, conforme mostrado aqui. Estes scripts formam o cliente, que também executa o código da página [index] — o mesmo código do servidor:
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]')
}
}
- Para compreender o que está a acontecer, é necessário perceber que o cliente [nuxt] não executará as linhas 22–24 (process.server=false);
- numa aplicação [Vue] clássica, a propriedade [value] na linha 10 permanece em 0. É por isso que, assim que o cliente navega para a página recebida, o valor apresentado passa a ser [0];
O valor gerado pelo servidor [nuxt] para a propriedade [value] era inútil.
5.2. A página [page1]
5.2.1. O armazenamento [Vuex]
Adicionámos uma pasta [store] ao projeto [nuxt-02]:

A presença desta pasta faz com que o [nuxt] implemente automaticamente um store [Vuex]. O ficheiro [index.js] implementa este store. Aqui, o ficheiro [index.js] tem o seguinte conteúdo:
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state, inc) {
state.counter += inc
}
}
[nuxt] implementa um armazenamento [Vuex] com base no conteúdo de [index.js]:
- linhas 1–3: definição do [state] do store. Este estado é devolvido por uma função. Aqui, o estado tem apenas uma propriedade, o contador na linha 2. A função exportada deve ser denominada [state];
- linhas 5–9: as operações possíveis no estado do store. Estas são chamadas [mutations]. Aqui, a mutação [increment] incrementa a propriedade [counter] num valor [inc]. O objeto exportado deve ser denominado [mutations];
O [store] Vuex implementado pelo [nuxt] está disponível em vários locais. Nas visualizações, está disponível na propriedade [this.$store].
5.2.2. O código da página
Tal como a página [index], a página [page1] irá apresentar um valor: o contador do Vuex store:
<!-- 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>
Comentários
- linhas 38–40: o servidor incrementará o contador em 25;
- linha 42: tanto o servidor como o cliente irão apresentar o valor do contador;
- linha 7: o valor do contador é exibido;
Ao ler este código, há dois aspetos que devem ser compreendidos:
- o código executado é o mesmo tanto para o servidor como para o cliente;
- o objeto [this] não é o mesmo: existe um [this] do lado do servidor e um [this] do lado do cliente;
Queremos saber se o [this.$store] do servidor é o mesmo que o [this.$store] do cliente. Uma vez que o servidor é executado primeiro (quando a aplicação arranca), isto resume-se a perguntar: o [store] inicializado pelo servidor é passado para o cliente?
5.2.3. Execução
Executamos o projeto [nuxt-02] e digitamos manualmente [localhost:81/nuxt-02/page1] para acionar o servidor. Tal como acontece com a página [index] no arranque:
- o servidor executa a página [page1.vue];
- envia a página gerada para o navegador. A página é exibida;
- os scripts do lado do cliente incorporados na página enviada assumem o controlo e executam a página [page1.vue] novamente;
- a página exibida é então modificada;
O resultado final é o seguinte:

Desta vez, o valor apresentado é, de facto, aquele definido pelo servidor e, visualmente, a página não «salta» devido a uma alteração do lado do cliente no valor apresentado pelo servidor. O que aconteceu desta vez?
O servidor executou a seguinte página [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>
- As linhas 30–32 foram executadas sem erros. Isto significa que, também no lado do servidor, [this.$store] se refere ao armazenamento [Vuex]. A linha 31 definiu o contador do armazenamento para 25;
- após o que a página foi enviada para o cliente;
Se olharmos para a página recebida pelo cliente, encontramos os seguintes 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>
- linha 47: o valor enviado pelo servidor;
- linha 60: vemos que o estado do armazenamento [Vuex] foi incorporado na página. Isto permitirá que o cliente, que será executado após receber a página, reconstrua um novo armazenamento [Vuex] com 25 como valor inicial do contador;
Após receber e apresentar a página do servidor, o cliente assume o controlo e executa a página [page1] por sua vez:
...
<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>
- linha 34: à propriedade [value] da linha 18 é atribuído o valor 25 do contador;
O armazenamento [nuxt] permite, portanto, que o servidor transmita informações ao cliente durante o carregamento inicial da página, quando esta é solicitada ao servidor. Note-se que, uma vez obtida esta página, o servidor deixa de estar envolvido e a aplicação funciona como uma aplicação [vue] clássica, no modo de página única.
5.3. A página [page2]
Na página [page2], demonstramos outra forma de:
- o servidor incluir informações calculadas na página;
- o cliente não altere essas informações;
5.3.1. O código da página
O código da página [page2] altera-se da seguinte forma:
<!-- 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>
- linha 7: a página exibe o valor de uma propriedade chamada [value];
- a propriedade [value] não existe como parte de um objeto devolvido pela função [data]. Aqui, esta função não existe. A propriedade [value] é criada dinamicamente pela linha 36;
- linha 25: a função [asyncData] é uma função [nuxt]. Como o próprio nome sugere, trata-se normalmente de uma função assíncrona. A sua função habitual é ir buscar dados externos. O [nuxt] garante que a página não é enviada para o navegador do cliente até que a função [asyncData] tenha renderizado os seus dados assíncronos;
- A função [asyncData] recebe o contexto [nuxt] como parâmetro. Este objeto é muito rico e fornece acesso a muita informação sobre a aplicação [nuxt]. Iremos explorar isto nas secções seguintes;
- Linha 31: Implementamos a função [asyncData] utilizando um [Promise] (consulte o documento |Introdução ao ECMAScript 6 por Exemplo|). O construtor desta classe aceita uma função assíncrona como parâmetro que:
- indica sucesso ao devolver dados com a função [resolve]. O objeto devolvido por esta função é automaticamente incluído nas propriedades [data] da página;
- sinaliza uma falha ao devolver um erro utilizando a função [reject];
- linha 34: simulamos uma função assíncrona utilizando a função [setTimeout]. Esta função devolve o objeto [{ value: 87 }] (linha 36) após um segundo (linha 31) utilizando a função [resolve], o que indica que a [Promise] foi bem-sucedida. O objeto devolvido pela função assíncrona é automaticamente incluído nas propriedades [data] da página. E é esta propriedade que a linha 7 apresenta;
- linha 27: veremos que a função [asyncData] é executada pelo servidor, mas não pelo cliente;
- linha 29: a propriedade [value] é inicializada pelo servidor;
Nota: o objeto [this] não é reconhecido na função [asyncData] porque o objeto que encapsula o componente [vue] ainda não foi criado;
5.3.2. Execução
Executamos o projeto [nuxt-02] e digitamos manualmente [localhost:81/nuxt-02/page2] para acionar o servidor. Tal como no lançamento inicial da página [index]:
- o servidor executa a página [page2.vue];
- envia a página gerada para o navegador. A página é exibida;
- os scripts do lado do cliente incorporados na página enviada assumem o controlo e executam novamente a página [page2.vue];
- a página exibida é então modificada;
O resultado final é o seguinte:

Desta vez, o valor apresentado é, de facto, aquele definido pelo servidor e, visualmente, a página não «salta» devido a uma alteração do lado do cliente no valor apresentado pelo servidor. O que aconteceu desta vez?
O servidor executou a seguinte página [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>
A linha 36 define o valor apresentado pela linha 7. Foi isto que o navegador do cliente recebeu. Mais concretamente, recebeu a seguinte 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>
- linha 48: vemos que o valor na página recebida é 87;
- linha 61: na resposta do servidor, vemos dois objetos: [data] e [state]:
- [state] é o estado do armazenamento [Vuex]. Este foi instanciado a partir do conteúdo da pasta [store] na aplicação [nuxt-02];
- [data] contém as propriedades criadas pelo servidor utilizando a função [asyncData]. Encontramos a propriedade [value: 87] criada pelo servidor. Os scripts do lado do cliente irão incorporar esta propriedade nas da página [page2];
Voltemos ao código da página [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>
- A linha 7 utiliza a propriedade [value]. No entanto, a página não define nenhuma propriedade com o nome [value]. Os scripts do lado do cliente criaram automaticamente esta propriedade utilizando o objeto [data: [{ value: 87 }]] recebido do servidor;
Os registos também mostram que a função [asyncData] não foi executada pelo cliente:

A função [asyncData] foi executada pelo servidor [1], mas não pelo cliente [2]. Além disso, é importante notar que as funções de ciclo de vida não são executadas pelo servidor até que a função [asyncData] tenha terminado. Podemos aumentar o tempo de espera dentro da função [asyncData] para verificar isto.
5.4. A página [page3]
Estamos a adicionar uma nova página [page3] à nossa aplicação:

5.4.1. O componente [navigation]
O componente [navigation] é modificado para permitir a navegação para a nova página:
<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. O código para [página3]
O código para a página [page3] é o seguinte:
<!-- 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>
- Linha 30: A função [fetch] comporta-se de forma semelhante à função [asyncData]:
- é executada antes das funções do ciclo de vida;
- o objeto [this] não é reconhecido nesta função;
- opera de forma assíncrona;
- o ciclo de vida não começa até que a função assíncrona tenha devolvido o seu resultado;
- o resultado é devolvido aqui pelo método [then] do [Promise], linha 43;
- A função [fetch] recebe o parâmetro [context]. Este representa o contexto [nuxt] atual;
- linha 30: entre as suas muitas propriedades, o objeto [context] tem uma propriedade [store] que representa o armazenamento [Vuex] da aplicação;
- linha 41: artificialmente, sinalizamos o sucesso do [Promise] após um segundo (ver documento |Introdução ao ECMAScript 6 através de exemplos|);
- linha 45: o método [then] é então executado. Aqui, o contador [store] é incrementado;
5.4.3. Execução
Executamos o projeto [nuxt-02] e digitamos manualmente [localhost:81/nuxt-02/page3] para acionar o servidor. Tal como acontece com a página [index] no arranque:
- o servidor executa a página [page3.vue];
- envia a página gerada para o navegador. A página é exibida;
- os scripts do lado do cliente incorporados na página enviada assumem o controlo e executam novamente a página [page3.vue];
- a página exibida é então modificada;
O resultado final é o seguinte:

O valor apresentado é, de facto, aquele definido pelo servidor e, visualmente, a página não «salta» devido a uma alteração do lado do cliente no valor apresentado pelo servidor. O que aconteceu desta vez?
O servidor executou a seguinte página [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>
- linha 45: a função assíncrona [fetch] é a primeira das funções acima a ser executada. Recebe como parâmetro um objeto chamado [context], que é o contexto [Nuxt] atual. Entre as muitas propriedades deste objeto, a propriedade [context.store] representa o store [Vuex];
- Linha 45: Na função assíncrona [fetch], o servidor define o contador do armazenamento para 28;
- linha 56: quando a função [created] é executada, o [nuxt] garante que a função assíncrona [fetch] tenha concluído o seu trabalho;
- linha 58: o valor do contador do store é atribuído à propriedade [value] na linha 27;
- linha 7: exibe o valor de [value], ou seja, o contador do store;
O navegador do cliente recebe a seguinte 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>
- linha 52: vemos que o valor na página recebida é 28;
- linha 65: na resposta do servidor, vemos que o servidor enviou o estado do armazenamento [Vuex] para o cliente. Com esta informação, os scripts do cliente poderão reconstruir um armazenamento [Vuex];
Os scripts do cliente, por sua vez, executarão o código da página [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>
- linha 58: a função [created] executada pelo cliente define o valor do contador na propriedade [value] na linha 27;
- a linha 7 exibe este valor. Uma vez que é igual ao enviado pelo servidor, não vemos a página «saltar» devido a uma alteração;
Os registos também mostram que a função [fetch] não foi executada pelo cliente:

A função [fetch] foi executada pelo servidor [1], mas não pelo cliente [2]. Além disso, note que as funções do ciclo de vida não são executadas pelo servidor até que a função [fetch] tenha terminado [3]. Podemos aumentar o tempo de espera dentro da função [fetch] para verificar isto.
As páginas [page1] e [page3] demonstraram dois métodos que utilizam o armazenamento [Vuex] para transmitir informações do servidor para o cliente. Poder-se-á questionar se são equivalentes. Iremos criar uma página [page4] para verificar isto.
5.5. A página [page4]
Estamos a adicionar uma nova página [page4] à nossa aplicação:

5.5.1. O componente [navigation]
O componente [navigation] foi modificado para permitir a navegação para a nova página:
<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. O código para [página4]
O código para a página [page4] é o seguinte:
<!-- 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>
- linha 30: o que era feito anteriormente na função [fetch] é agora feito no método [beforeCreate]. Utilizamos o par async (linha 30) / await (linha 36) para aguardar que a função assíncrona termine;
- linha 36: recuperamos o resultado da função assíncrona devolvido na linha 41 após 10 segundos (linha 42);
- linhas 50–54: no método [created], que é executado tanto no servidor como no cliente, o contador é atribuído à propriedade [value] da página;
5.5.3. Execução
Execute o projeto [nuxt-02] e digite manualmente [localhost:81/nuxt-02/page4] para acionar o servidor. Tal como no lançamento inicial da página [index]:
- o servidor executa a página [page4.vue];
- envia a página gerada para o navegador. A página é exibida;
- os scripts do lado do cliente incorporados na página enviada assumem o controlo e executam novamente a página [page4.vue];
- a página exibida é então modificada;
O resultado final é o seguinte:

Contrariamente ao esperado, o valor apresentado em [2] não é 52. O que aconteceu?
Os registos são os seguintes:

Podemos ver que em [1], o registo que indica o fim da ação assíncrona não foi exibido. A função [created], que exibe o valor do contador, mostra 0. Tudo isto sugere que [nuxt] não esperou que a ação assíncrona terminasse.
Se voltarmos ao terminal do VSCode utilizado para iniciar a aplicação, encontramos os registos [3-4]. Podemos ver que a função assíncrona foi, de facto, executada no lado do servidor.
Em última análise, a função [beforeCreate] foi de facto executada na totalidade no lado do servidor, mas o [Nuxt] não esperou que a execução terminasse antes de enviar a página para o navegador do cliente, apesar de esperar que a função [fetch] fosse concluída. Por conseguinte, este é o método que deve utilizar se pretender que o servidor inicialize um armazenamento [Vuex].
5.6. Navegação na aplicação [Vue]
Mostrámos o que acontece quando cada uma das páginas [index, page1, page2, page3, page4] é inicialmente carregada pelo servidor. Na prática, não é isso que acontece: em condições normais de funcionamento, apenas a página [index] é obtida do servidor. Vejamos as três páginas neste caso:
Página [index]

Já explicámos este resultado na secção «link».
Agora, vamos clicar no link [Página 1]:

O valor apresentado é 0. Era 25 quando a página foi solicitada pela primeira vez ao servidor, digitando manualmente o seu URL. A explicação é simples. O código executado é o seguinte:
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)
},
A linha 6 foi a que definiu o contador para 25. Como a página não foi solicitada ao servidor, as linhas 5–7 não foram executadas e o contador no armazenamento [Vuex] permaneceu em 0.
Agora, vamos clicar no link [Página 2]:

Desta vez, nenhum valor é exibido e também vemos um aviso nos registos da consola:

- Em [1], vemos que a função [asyncData] foi executada pelo cliente. É sempre assim:
- é executada pelo servidor se a página for solicitada ao servidor. Neste caso, não é executada pelo cliente;
- então, sempre que a página for o destino da rota atual do cliente;
- em [2]: [nuxt] emite um aviso porque o modelo da página contém uma expressão reativa {{ value }}, mesmo que a página não tenha a propriedade [value];
Vamos rever o código executado pelo cliente:
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 })
})
}
},
- A linha 3 mostrou que a função [asyncData] foi executada pelo cliente mesmo antes das funções do ciclo de vida;
- A linha 5 impediu que o resto do código que criava a propriedade [value] fosse executado, porque esse código é executado pelo cliente;
Agora, vamos passar para a página [page3]:

- em [2], tínhamos 28 quando a página foi servida pelo servidor. Não é esse o caso aqui;
- Em [4], vemos que a função [fetch] foi executada no lado do cliente;
Vamos examinar o código executado pelo cliente:
...
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é')
})
}
},
...
- O cliente executa o método [fetch], linha 2;
- as linhas 6–21 não são executadas porque a condição [process.server] é falsa. Portanto, a linha 17, que define o contador para 28, não é executada. Ele permanece em zero. É por isso que o cliente exibe 0 em vez de 28;
Agora, passemos à página [page4]. Obtemos o seguinte resultado:

- em [2], o valor do contador;
- em [3], os registos do cliente;
O código executado pelo cliente é o seguinte:
...
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)
},
...
- As linhas 12–26 não são executadas pelo cliente porque a condição [process.server] na linha 12 é falsa. Portanto, o contador no armazenamento [Vuex] é 0 (o seu valor inicial no armazenamento), e este é o valor apresentado na página;
Pode estar a perguntar-se o que acontece quando se comentam as instruções [if] nas linhas 12 e 26. Aqui está a resposta:
- Desta vez, o cliente executa as linhas 14–25, mas o [nuxt] não espera que a função assíncrona termine (como faz no servidor) e, por isso, deixa o contador em 0;
- Após 10 segundos, a função assíncrona é concluída e o contador é definido para 52 na linha 23;
- ao navegar de volta para a página [page4], o valor 52 é então exibido;
5.7. Resumo
A partir dos nossos vários testes, podemos concluir o seguinte:
- se a página [index] tiver de conter dados externos, esses dados podem ser obtidos pelo servidor utilizando a função [asyncData];
- se o servidor precisar de inicializar um armazenamento [Vuex] com dados externos ao carregar a página [index], fá-lo-á na função [fetch];
- a página gerada pelo servidor e a página gerada pelo cliente devem ser idênticas para evitar o efeito de «trepidação» causado pela substituição visual, pelo lado do cliente, da página enviada pelo servidor e inicialmente exibida;
Iremos explorar outros aspetos do [Nuxt] através de um novo exemplo.