5. Exemplo [nuxt-02]: páginas do servidor e do cliente
Neste projeto, mostramos:
- que a página construída pelo cliente pode ser visualmente diferente daquela recebida do servidor. Temos, assim, uma mudança rápida de página, percetível pelo utilizador, o que prejudica a ergonomia da aplicação. Trata-se, portanto, de uma opção a evitar;
- uma solução para que a página do cliente recrie a mesma página que a enviada pelo servidor;
O projeto [nuxt-02] é obtido inicialmente através da cópia do projeto [nuxt-01].

É adicionada ao projeto uma pasta [store], bem como 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] passa a ser o seguinte:
<!-- página principal -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem-->
<b-alert slot="right" show variant="warning"> Home - value= {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Home',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[home beforeCreate]')
},
created() {
// cliente e servidor
console.log('[home created]')
// apenas servidor
if (process.server) {
this.value = 10
}
// cliente e servidor
console.log('value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[home beforeMount]')
},
mounted() {
// apenas cliente
console.log('[home mounted]')
}
}
</script>
Comentários
- linha 7: a página [index] irá apresentar o valor da sua propriedade [value] (linha 28);
- linhas 36-45: é importante lembrar aqui que a função [created] é executada tanto no lado do servidor como no lado do cliente. Nas linhas 40-42, o servidor definirá o valor da propriedade [value] para 10. O cliente, por sua vez, não altera este valor. Queremos simplesmente saber se este valor é mantido pelo cliente. Vamos descobrir que não;
5.1.2. Execução
Alteramos o ficheiro [/nuxt.config.js] para executar o projeto [nuxt-02]:
...
// diretório do código-fonte
srcDir: 'nuxt-02',
// router
router: {
// raiz dos URL da aplicação
base: '/nuxt-02/'
},
// servidor
server: {
// porta de serviço, 3000 por predefinição
port: 81,
// endereços de rede em escuta, por predefinição localhost: 127.0.0.1
// 0.0.0.0 = todos os endereços de rede do computador
host: 'localhost'
}
...
Executamos o projeto [1]:

A página [index] é então apresentada como [2-3]. Apresenta o valor [10] durante alguns instantes e, em seguida, apresenta o valor [0]. O que aconteceu?
Passo 1
É o servidor que é executado primeiro. Este executa o código da página [index]:
export default {
name: 'Home',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[home beforeCreate]')
},
created() {
// cliente e servidor
console.log('[home created]')
// apenas servidor
if (process.server) {
this.value = 10
}
// cliente e servidor
console.log('value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[home beforeMount]')
},
mounted() {
// apenas cliente
console.log('[home mounted]')
}
}
- devido à linha 23, a propriedade [value] da linha 10 assume o valor 10;
É possível verificar isso consultando o código-fonte da página recebida pelo navegador (opção [code source] 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 receção da página, os scripts das linhas 57-60 assumem o controlo e alteram o comportamento da página recebida, ou mesmo as informações apresentadas, como neste caso. Estes scripts constituem o cliente, que também executa o código da página [index], o mesmo código que o servidor:
export default {
name: 'Home',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[home beforeCreate]')
},
created() {
// cliente e servidor
console.log('[home created]')
// apenas servidor
if (process.server) {
this.value = 10
}
// cliente e servidor
console.log('value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[home beforeMount]')
},
mounted() {
// apenas cliente
console.log('[home mounted]')
}
}
- para compreender o que se passa, é necessário perceber que o cliente [nuxt] não executará as linhas 22 a 24 (process.server=false);
- numa aplicação [vue] clássica, a propriedade [value] da linha 10 permanece em 0. É por isso que, assim que o cliente acede à página recebida, o valor apresentado passa a ser [0];
O valor gerado pelo servidor [nuxt] para a propriedade [value] não serviu para nada.
5.2. A página [page1]
5.2.1. A loja [Vuex]
Adicionámos uma pasta [store] ao projeto [nuxt-02]:

A existência deste ficheiro faz com que o [nuxt] implemente automaticamente um armazenamento [Vuex]. É o ficheiro [index.js] que implementa este armazenamento. Aqui, o ficheiro [index.js] é o seguinte:
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state, inc) {
state.counter += inc
}
}
O [nuxt] implementa um store [Vuex] a partir do conteúdo do [index.js]:
- linhas 1-3: definição do estado [state] do store. Este estado é devolvido por uma função. Aqui, o estado tem apenas uma propriedade, o contador da linha 2. A função exportada deve chamar-se [state];
- linhas 5-9: as operações possíveis no estado do store. Chamam-se [mutations]. Aqui, a modificação [increment] permite incrementar a propriedade [counter] numa quantidade [inc]. O objeto exportado deve chamar-se [mutations];
O [store] Vuex implementado por [nuxt] está disponível em vários locais. Nas vistas, 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 do contador do Vuex:
<!-- página 1 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem-->
<b-alert slot="right" show variant="primary"> Page 1 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[home beforeCreate]')
},
created() {
// cliente e servidor
console.log('[home created]')
// apenas servidor
if (process.server) {
this.$store.commit('increment', 25)
}
// cliente e servidor
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[home beforeMount]')
},
mounted() {
// apenas cliente
console.log('[home mounted]')
}
}
</script>
Comentários
- linhas 38-40: o servidor irá 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 é apresentado;
Ao ler este código, é preciso compreender duas coisas:
- o código executado é o mesmo tanto para o servidor como para o cliente;
- o objeto [this], por sua vez, não é o mesmo: existe uma versão [this] no lado do servidor e outra no lado do cliente, [client];
Queremos saber se o [this.$store] do servidor é o mesmo que o [this.$store] do cliente. Como é o servidor que é executado primeiro (ao iniciar a aplicação), isto equivale a perguntar: será que o [store] inicializado pelo servidor é transmitido ao cliente?
5.2.3. Execução
Executamos o projeto [nuxt-02] e digitamos manualmente [localhost:81/nuxt-02/page1] para que o servidor seja solicitado. Tal como no arranque da página [index]:
- o servidor executa a página [page1.vue];
- envia a página gerada para o navegador. Esta é apresentada;
- os scripts do cliente incorporados na página enviada assumem o controlo e executam novamente a página [page1.vue];
- a página apresentada é então alterada;
O resultado final é o seguinte:

Desta vez, o valor apresentado é efetivamente aquele definido pelo servidor e, visualmente, não se observa a página a «oscilar» devido a uma alteração, por parte do cliente, do valor apresentado pelo servidor. O que aconteceu desta vez?
O servidor executou a página [page1] seguinte:
...
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page1 beforeCreate]')
},
created() {
// cliente e servidor
console.log('[page1 created]')
// apenas servidor
if (process.server) {
this.$store.commit('increment', 25)
}
// cliente e servidor
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[page1 beforeMount]')
},
mounted() {
// apenas cliente
console.log('[page1 mounted]')
}
}
</script>
- as linhas 30-32 foram executadas sem erros. O que significa que, também do lado do servidor, [this.$store] designa o registo [Vuex]. A linha 31 aumentou o contador do registo para 25;
- feito isto, a página foi enviada ao cliente;
Se analisarmos 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: verifica-se que o estado do armazenamento [Vuex] foi incorporado na página. Isto permitirá ao cliente, que será executado após a receção da página, reconstituir um novo armazenamento [Vuex] com 25 como valor inicial do contador;
Após a receção e a visualização da página recebida do servidor, o cliente assume o controlo e, por sua vez, executa a página [page1]:
...
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page1',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page1 beforeCreate]')
},
created() {
// cliente e servidor
console.log('[page1 created]')
// apenas servidor
if (process.server) {
this.$store.commit('increment', 25)
}
// cliente e servidor
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[page1 beforeMount]')
},
mounted() {
// apenas cliente
console.log('[page1 mounted]')
}
}
</script>
- linha 34: a propriedade [value] da linha 18 recebe, por sua vez, o valor 25 do contador;
O «store» de [nuxt] permite, assim, que o servidor transmita informações ao cliente durante o carregamento inicial da página, quando esta é solicitada ao servidor. Recorde-se que, uma vez obtida esta página, o servidor já não é solicitado e a aplicação funciona como uma aplicação [vue] clássica, no modo SAP.
5.3. A página [page2]
Na página [page2], mostramos outra forma de fazer com que:
- o servidor inclua informações calculadas na página;
- o cliente não as altere;
5.3.1. O código da página
O código da página [page2] evolui da seguinte forma:
<!-- página 2 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem -->
<b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page2',
// componentes utilizados
components: {
Layout,
Navigation
},
asyncData(context) {
// quem executa este código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna-se uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// este resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
// registo
console.log('asynData terminée')
}, 1000)
})
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page2 beforeCreate]')
},
created() {
// cliente e servidor
console.log('[page2 created]')
},
beforeMount() {
// apenas cliente
console.log('[page2 beforeMount]')
},
mounted() {
// apenas cliente
console.log('[page2 mounted]')
}
}
</script>
- linha 7: a página apresenta o valor de uma propriedade denominada [value];
- a propriedade [value] não existe como elemento de um objeto devolvido pela função [data]. Neste caso, essa 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 indica, trata-se normalmente de uma função assíncrona. A sua função habitual é ir buscar dados externos. A função [nuxt] garante que a página não é enviada para o navegador do cliente antes de a função [asyncData] ter devolvido os seus dados assíncronos;
- a função [asyncData] recebe como parâmetro o contexto [nuxt]. Este objeto é muito abrangente e dá acesso a muita informação sobre a aplicação [nuxt]. Iremos descobrir isso nas secções seguintes;
- linha 31: implementamos a função [asyncData] com um [Promise] (ver documento |Introdução à linguagem ECMASCRIPT 6 através de exemplos|). O construtor desta classe aceita como parâmetro uma função assíncrona 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;
- indica um erro ao devolver um erro com a função [reject];
- linha 34: simula-se uma função assíncrona com a função [setTimeout]. Esta função devolve o objeto [{ value: 87 }] (linha 36) ao fim de um segundo (linha 31) graças à função [resolve], que indica o sucesso da função [Promise]. O objeto devolvido pela função assíncrona é incluído automaticamente nas propriedades [data] da página. E é, portanto, esta propriedade que a linha 7 apresenta;
- linha 27: vamos descobrir que a função [asyncData] é executada pelo servidor, mas não pelo cliente;
- linha 29: a inicialização da propriedade [value] é feita pelo servidor;
Nota: o objeto [this] não é reconhecido na função [asyncData], uma vez que o objeto que encapsula o componente [vue] ainda não foi criado;
5.3.2. Execução
Executa-se o projeto [nuxt-02] e introduz-se manualmente [localhost:81/nuxt-02/page2] para que o servidor seja solicitado. Tal como no início, para a página [index]:
- o servidor executa a página [page2.vue];
- envia a página gerada para o navegador. Esta é apresentada;
- os scripts do cliente incorporados na página enviada assumem o controlo e executam novamente a página [page2.vue];
- a página apresentada é então alterada;
O resultado final é o seguinte:

Desta vez, o valor apresentado é efetivamente aquele definido pelo servidor e, visualmente, não se observa a página a «oscilar» devido a uma alteração, por parte do cliente, do valor apresentado pelo servidor. O que aconteceu desta vez?
O servidor executou a página seguinte: [page2]:
<!-- página 2 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem -->
<b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page2',
// componentes utilizados
components: {
Layout,
Navigation
},
asyncData(context) {
// quem executa este código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna-se uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// este resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
// registo
console.log('asynData terminée')
}, 1000)
})
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page2 beforeCreate]')
},
created() {
// cliente e servidor
console.log('[page2 created]')
},
beforeMount() {
// apenas cliente
console.log('[page2 beforeMount]')
},
mounted() {
// apenas cliente
console.log('[page2 mounted]')
}
}
</script>
Foi a linha 36 que definiu o valor apresentado pela linha 7. Foi, portanto, isto que o navegador do cliente recebeu. Mais precisamente, 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 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: verifica-se que o valor na página recebida é 87;
- linha 61: na resposta do servidor, vemos dois objetos: [data] e [state]:
- [state] é o estado do store [Vuex]. Este foi instanciado a partir do conteúdo da pasta [store] da aplicação [nuxt-02];
- O [data] contém as propriedades criadas pelo servidor através da função [asyncData]. Encontramos aqui a propriedade [value : 87] criada pelo servidor. Os scripts do cliente irão integrar esta propriedade nas da página [page2];
Voltemos ao código da página [page2]:
<!-- página 2 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem -->
<b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page2',
// componentes utilizados
components: {
Layout,
Navigation
},
asyncData(context) {
// quem executa este código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna-se uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// este resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
// registo
console.log('asynData terminée')
}, 1000)
})
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page2 beforeCreate]')
},
created() {
// cliente e servidor
console.log('[page2 created]')
},
beforeMount() {
// apenas cliente
console.log('[page2 beforeMount]')
},
mounted() {
// apenas cliente
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]. Contudo, os scripts do cliente criaram automaticamente esta propriedade graças ao objeto [data: [{ value: 87 }]] recebido do servidor;
Os registos mostram, além disso, 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, é de salientar que as funções do ciclo de vida não são executadas pelo servidor antes do fim da função [asyncData]. É possível aumentar o tempo de espera na função [asyncData] para verificar isso.
5.4. A página [page3]
Adicionamos uma nova página [page3] à nossa aplicação:

5.4.1. O componente [navigation]
O componente [vavigation] é alterado para permitir a navegação para a nova página:
<template>
<!-- menu Bootstrap com três opções -->
<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 de [page3]
O código da página [page3] é o seguinte:
<!-- página 3 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem -->
<b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page3',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// quem executa este código?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// altera-se o store
context.store.commit('increment', 28)
})
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page3 beforeCreate]')
},
created() {
// cliente e servidor
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[page3 beforeMount]')
},
mounted() {
// apenas cliente
console.log('[page3 mounted]')
}
}
</script>
- linha 30: a função [fetch] tem um comportamento semelhante ao da função [asyncData]:
- é executada antes das funções do ciclo de vida;
- o objeto [this] não é reconhecido nesta função;
- o seu funcionamento é assíncrono;
- o ciclo de vida não começa enquanto a função assíncrona não tiver devolvido o seu resultado;
- o resultado é aqui devolvido pelo método [then] da função [Promise], linha 43;
- a função [fetch] recebe o parâmetro [context]. Este representa o contexto [nuxt] do momento;
- linha 30: entre as suas numerosas propriedades, o objeto [context] possui uma propriedade [store] que representa o store [Vuex] da aplicação;
- linha 41: artificialmente, sinaliza-se o sucesso do [Promise] ao fim de um segundo (ver documento |Introdução à linguagem ECMASCRIPT 6 através de exemplos|);
- linha 45: o método [then] é então executado. Nele, incrementa-se o contador do [store];
5.4.3. Execução
Executa-se o projeto [nuxt-02] e digita-se manualmente [localhost:81/nuxt-02/page3] para que o servidor seja solicitado. Tal como no arranque para a página [index]:
- o servidor executa a página [page3.vue];
- envia a página gerada para o navegador. Esta é apresentada;
- os scripts do cliente incorporados na página enviada assumem o controlo e executam novamente a página [page3.vue];
- a página apresentada é então alterada;
O resultado final é o seguinte:

O valor apresentado é, de facto, aquele definido pelo servidor e, visualmente, não se observa que a página «salte» devido a uma alteração, por parte do cliente, do valor apresentado pelo servidor. O que aconteceu desta vez?
O servidor executou a página seguinte, [page3]:
<!-- página 3 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem -->
<b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page3',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// quem executa este código?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna-se uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// altera-se o store
context.store.commit('increment', 28)
// registo
console.log('fetch commit terminé')
})
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page3 beforeCreate]')
},
created() {
// cliente e servidor
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[page3 beforeMount]')
},
mounted() {
// apenas cliente
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 denominado [context], que corresponde ao contexto [nuxt] do momento. Entre as inúmeras propriedades deste objeto, a propriedade [context.store] representa o armazenamento [Vuex];
- linha 45: na função assíncrona [fetch], o servidor define o contador do armazenamento temporário para 28;
- linha 56: quando a função [created] é executada, a [nuxt] garante que a função assíncrona [fetch] tenha concluído o seu trabalho;
- linha 58: o valor do contador da persiana é atribuído à propriedade [value] da linha 27;
- linha 7: exibição do valor de [value], ou seja, do 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: verifica-se que o valor na página recebida é 28;
- linha 65: na resposta do servidor, verifica-se que o servidor enviou ao cliente o estado [state] do armazenamento [Vuex]. Graças a esta informação, os scripts do cliente poderão reconstituir um armazenamento [Vuex];
Os scripts do cliente, por sua vez, irão executar o código da página [page3]:
<!-- página 3 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem -->
<b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page3',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
fetch(context) {
// quem executa este código?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna-se uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// altera-se o store
context.store.commit('increment', 28)
// registo
console.log('fetch commit terminé')
})
}
},
// ciclo de vida
beforeCreate() {
// cliente e servidor
console.log('[page3 beforeCreate]')
},
created() {
// cliente e servidor
this.value = this.$store.state.counter
console.log('[page3 created], value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[page3 beforeMount]')
},
mounted() {
// apenas cliente
console.log('[page3 mounted]')
}
}
</script>
- linha 58: a função [created] executada pelo cliente atribui o valor do contador à propriedade [value] da linha 27;
- a linha 7 apresenta esse valor. Uma vez que é o mesmo que o enviado pelo servidor, não se observa a página a «tremer» devido a uma alteração;
Os registos mostram, além disso, 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, é de salientar que as funções do ciclo de vida não são executadas pelo servidor antes do fim da função [fetch] [3]. É possível aumentar o tempo de espera na função [fetch] para verificar isso.
As páginas [page1] e [page3] apresentaram dois métodos que utilizam o store [Vuex] para transmitir informação do servidor para o cliente. É possível questionar se estes métodos são equivalentes. Criamos uma página [page4] para verificar isso.
5.5. A página [page4]
Adicionamos uma nova página [page4] à nossa aplicação:

5.5.1. O componente [navigation]
O componente [navigation] é alterado para permitir a navegação para a nova página:
<template>
<!-- menu Bootstrap com cinco opções -->
<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 de [page4]
O código da página [page4] é o seguinte:
<!-- página 4 -->
<template>
<Layout :left="true" :right="true">
<!-- navegação -->
<Navigation slot="left" />
<!-- mensagem -->
<b-alert slot="right" show variant="secondary"> Page 4 - value = {{ value }} </b-alert>
</Layout>
</template>
<script>
/* eslint-disable no-console */
import Navigation from '@/components/navigation'
import Layout from '@/components/layout'
export default {
name: 'Page4',
// componentes utilizados
components: {
Layout,
Navigation
},
data() {
return {
value: 0
}
},
// ciclo de vida
async beforeCreate() {
// cliente e servidor
console.log('[page4 beforeCreate]')
// apenas para o servidor
if (process.server) {
// executa-se a função assíncrona
const valeur = await new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simula-se com uma espera de 10 segundos
setTimeout(() => {
// sucesso — devolve-se o valor do contador
resolve(52)
}, 10000)
})
// Altera-se o registo
this.$store.commit('increment', valeur)
// registo
console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
}
},
created() {
// cliente e servidor
this.value = this.$store.state.counter
console.log('[page4 created], value=', this.value)
},
beforeMount() {
// apenas cliente
console.log('[page4 beforeMount]')
},
mounted() {
// apenas cliente
console.log('[page4 mounted]')
}
}
</script>
- linha 30: o que antes era feito na função [fetch] é agora feito no método [beforeCreate]. Utiliza-se o par async (linha 30) / await (linha 36) para aguardar o fim da função assíncrona;
- linha 36: recupera-se o resultado da função assíncrona, devolvido na linha 41 após 10 segundos (linha 42);
- linhas 50-54: no método [created], executado tanto no lado do servidor como no lado do cliente, o contador é atribuído à propriedade [value] da página;
5.5.3. Execução
Executa-se o projeto [nuxt-02] e introduz-se manualmente [localhost:81/nuxt-02/page4] para que o servidor seja solicitado. Tal como no arranque da página [index]:
- o servidor executa a página [page4.vue];
- envia a página gerada para o navegador. Esta é apresentada;
- os scripts do cliente incorporados na página enviada assumem o controlo e executam novamente a página [page4.vue];
- a página apresentada é então alterada;
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:

Pode-se observar que, em [1], o registo de fim da ação assíncrona não foi apresentado. A função [created], que apresenta o valor do contador, apresenta 0. Tudo isto leva a crer que [nuxt] não aguardou o fim da ação assíncrona.
Se voltarmos ao terminal da função VSCode, que serviu para iniciar a aplicação, encontramos os registos da função [3-4]. Verifica-se que a função assíncrona foi efetivamente executada no lado do servidor.
No final, a função [beforeCreate] foi efetivamente executada na íntegra no lado do servidor, mas [nuxt] não esperou pela conclusão da sua execução para enviar a página ao navegador do cliente, ao passo que espera sim pela conclusão da função [fetch]. É, portanto, este método que deve ser utilizado se se pretender que o servidor inicialize um store [Vuex].
5.6. Navegação na aplicação [vue]
Mostrámos o que acontecia quando cada uma das páginas [index, page1, page2, page3, page4] era carregada inicialmente pelo servidor. Na prática, não é isso que acontece: em funcionamento normal, apenas a página [index] é procurada no servidor. Vejamos as três páginas neste caso:
página [index]

Já explicámos este resultado no parágrafo sobre links.
Agora, cliquemos no link [Page 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() {
// cliente e servidor
console.log('[page1 created]')
// apenas servidor
if (process.server) {
this.$store.commit('increment', 25)
}
// cliente e servidor
this.value = this.$store.state.counter
console.log('value=', this.value)
},
Era a linha 6 que colocava o contador em 25. Como a página não foi solicitada ao servidor, as linhas 5 a 7 não foram executadas e o contador do store [Vuex] permaneceu em 0.
Agora, vamos clicar na ligação [Page 2]:

Desta vez, não é apresentado qualquer valor e, além disso, surge um aviso nos registos da consola:

- Em [1], descobrimos que a função [asyncData] foi executada pelo cliente. É sempre assim:
- é executada pelo servidor se a página for solicitada ao servidor. Nesse caso, não é executada pelo cliente;
- e sempre que a página for o destino da rota atual do cliente;
- em [2]: [nuxt] emite um aviso porque, no modelo da página, existe uma expressão reativa {{ value }}, embora a página não tenha a propriedade [value];
Recorde-se o código executado pelo cliente:
asyncData() {
// quem executa este código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna-se uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui teríamos uma função assíncrona — mas não é o caso aqui
// este resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
})
}
},
- a linha 3 permitiu verificar que a função [asyncData] foi executada pelo cliente antes mesmo das funções do ciclo de vida;
- a linha 5 impediu a execução do resto do código que criava a propriedade [value], uma vez que esse código é executado pelo cliente;
Passemos agora à página [page3]:

- em [2], tínhamos 28 quando a página foi fornecida pelo servidor. Aqui, não é esse o caso;
- em [4], vemos que a função [fetch] foi executada do lado do cliente;
Vamos analisar o código executado pelo cliente:
...
fetch(context) {
// quem executa este código?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna-se uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamo-la com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// altera-se o store
context.store.commit('increment', 28)
// registo
console.log('fetch commit terminé')
})
}
},
...
- o cliente executa o método [fetch], linha 2;
- as linhas 6 a 21 não são executadas porque a condição [process.server] é falsa. Por isso, a linha 17, que define o contador para 28, não é executada. O contador permanece em zero. É por isso que o cliente apresenta 0 em vez de 28;
Passemos agora à 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
}
},
// ciclo de vida
async beforeCreate() {
// cliente e servidor
console.log('[page4 beforeCreate]')
// apenas para o servidor
if (process.server) {
// executa-se a função assíncrona
const valeur = await new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simula-se com uma espera de 10 segundos
setTimeout(() => {
// sucesso — devolve-se o valor do contador
resolve(52)
}, 10000)
})
// Altera-se o registo
this.$store.commit('increment', valeur)
// registo
console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
}
},
created() {
// cliente e servidor
this.value = this.$store.state.counter
console.log('[page4 created], value=', this.value)
},
...
- as linhas 12-26 não são executadas pelo cliente, uma vez que a condição [process.server] da linha 12 é falsa. Assim, o contador do registo [Vuex] tem o valor 0 (o seu valor inicial no registo) e é esse valor que a página apresenta;
Podemos perguntar-nos o que acontece quando se coloca em comentário a condição [if] das linhas 12 e 26. Eis a resposta:
- desta vez, o cliente executa as linhas 14-25, mas o [nuxt] não aguarda o fim da função assíncrona (tal como no servidor) e, por isso, deixa o contador em 0;
- após 10 s, a função assíncrona termina e o contador é definido para 52 na linha 23;
- quando se navega novamente para a página [page4], é então o valor 52 que é apresentado;
5.7. Résumé
Dos nossos diversos testes, destacam-se os seguintes pontos:
- se a página [index] tiver de conter dados externos, estes podem ser recuperados pelo servidor através da função [asyncData];
- se o servidor tiver de inicializar um store [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, se quisermos evitar o efeito de «salto» provocado pelo facto de a página do cliente substituir visualmente a página enviada pelo servidor e inicialmente apresentada;
Vamos explorar outros aspetos da função [nuxt] através de um novo exemplo.