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, então, uma mudança rápida de página, perceptível pelo usuário, o que prejudica a usabilidade do aplicativo. Portanto, essa é uma opção a ser evitada;
- uma solução para que a página do cliente recrie a mesma página enviada pelo servidor;
O projeto [nuxt-02] é obtido inicialmente por meio da cópia do projeto [nuxt-01].

Uma pasta [store] é adicionada ao projeto, juntamente com duas novas páginas. Voltaremos a esse 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]')
// somente servidor
if (process.server) {
this.value = 10
}
// cliente e servidor
console.log('value=', this.value)
},
beforeMount() {
// somente cliente
console.log('[home beforeMount]')
},
mounted() {
// somente cliente
console.log('[home mounted]')
}
}
</script>
Comentários
- linha 7: a página [index] exibirá o valor de sua propriedade [value] (linha 28);
- linhas 36-45: é importante lembrar aqui que a função [created] é executada tanto no lado do servidor quanto no lado do cliente. Nas linhas 40-42, o servidor definirá o valor da propriedade [value] como 10. O cliente, por sua vez, não altera esse valor. Queremos simplesmente saber se esse valor é mantido pelo cliente. Vamos descobrir que não;
5.1.2. Execução
Alteramos o arquivo [/nuxt.config.js] para executar o projeto [nuxt-02]:
...
// diretório do código-fonte
srcDir: 'nuxt-02',
// roteador
router: {
// raiz dos URL do aplicativo
base: '/nuxt-02/'
},
// servidor
server: {
// porta de serviço, 3000 por padrão
port: 81,
// endereços de rede monitorados, por padrão localhost: 127.0.0.1
// 0.0.0.0 = todos os endereços de rede da máquina
host: 'localhost'
}
...
Executamos o projeto [1]:

A página [index] é então exibida como [2-3]. Ela exibe o valor [10] por alguns instantes e, em seguida, exibe o valor [0]. O que aconteceu?
Etapa 1
O servidor é executado primeiro. Ele 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]')
// somente servidor
if (process.server) {
this.value = 10
}
// cliente e servidor
console.log('value=', this.value)
},
beforeMount() {
// somente cliente
console.log('[home beforeMount]')
},
mounted() {
// somente cliente
console.log('[home mounted]')
}
}
- devido à linha 23, a propriedade [value] da linha 10 assume o valor 10;
É possível verificar isso observando 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;
etapa 2
Sabemos que, após o recebimento da página, os scripts das linhas 57 a 60 assumem o controle e alteram o comportamento da página recebida, inclusive as informações exibidas, como neste caso. Esses 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',
// 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]')
// somente servidor
if (process.server) {
this.value = 10
}
// cliente e servidor
console.log('value=', this.value)
},
beforeMount() {
// somente cliente
console.log('[home beforeMount]')
},
mounted() {
// somente cliente
console.log('[home mounted]')
}
}
- para entender o que está acontecendo, é preciso compreender que o cliente [nuxt] não executará as linhas 22 a 24 (process.server=false);
- em uma aplicação [vue] clássica, a propriedade [value] da linha 10 permanece em 0. É por isso que, assim que o cliente acessa a página recebida, o valor exibido 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]
Adicionamos uma pasta [store] ao projeto [nuxt-02]:

A presença desse arquivo faz com que, automaticamente, o [nuxt] implemente um store [Vuex]. É o arquivo [index.js] que implementa esse store. Aqui, o arquivo [index.js] é o seguinte:
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state, inc) {
state.counter += inc
}
}
[nuxt] implementa um store [Vuex] a partir do conteúdo de [index.js]:
- linhas 1-3: definição do estado [state] do store. Esse estado é retornado por uma função. Aqui, o estado possui apenas uma propriedade, o contador da linha 2. A função exportada deve se chamar [state];
- linhas 5-9: as operações possíveis no estado do store. Elas são chamadas de [mutations]. Aqui, a modificação [increment] permite incrementar a propriedade [counter] em uma quantidade [inc]. O objeto exportado deve se chamar [mutations];
O [store] Vuex implementado por [nuxt] está disponível em diferentes locais. Nas visualizações, ele está disponível na propriedade [this.$store].
5.2.2. O código da página
Assim como a página [index], a página [page1] exibirá um valor, o do contador do store 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]')
// somente servidor
if (process.server) {
this.$store.commit('increment', 25)
}
// cliente e servidor
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// somente cliente
console.log('[home beforeMount]')
},
mounted() {
// somente cliente
console.log('[home mounted]')
}
}
</script>
Comentários
- linhas 38-40: o servidor incrementará o contador em 25;
- linha 42: tanto o servidor quanto o cliente exibirão o valor do contador;
- linha 7: o valor do contador é exibido;
Ao ler este código, é preciso entender duas coisas:
- o código executado é o mesmo tanto para o servidor quanto para o cliente;
- o objeto [this], por sua vez, não é o mesmo: há 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 o aplicativo), isso equivale a perguntar: o [store] inicializado pelo servidor é transmitido ao cliente?
5.2.3. Execução
Executamos o projeto [nuxt-02] e digitamos [localhost:81/nuxt-02/page1] manualmente para que o servidor seja acionado. Assim como na inicialização da página [index]:
- o servidor executa a página [page1.vue];
- envia a página gerada ao navegador. Ela é exibida;
- os scripts do cliente incorporados na página enviada assumem o controle e executam novamente a página [page1.vue];
- a página exibida é então modificada;
O resultado final é o seguinte:

Desta vez, o valor exibido é, de fato, aquele definido pelo servidor e, visualmente, não se observa a página “pular” devido a uma alteração feita pelo cliente no valor exibido pelo servidor. O que aconteceu desta vez?
O servidor executou a página [page1] a seguir:
...
<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]')
// somente servidor
if (process.server) {
this.$store.commit('increment', 25)
}
// cliente e servidor
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// somente cliente
console.log('[page1 beforeMount]')
},
mounted() {
// somente cliente
console.log('[page1 mounted]')
}
}
</script>
- as linhas 30 a 32 foram executadas sem erros. Isso significa que, também no lado do servidor, [this.$store] se refere ao store [Vuex]. A linha 31 aumentou o contador do store para 25;
- feito isso, a página foi enviada ao cliente;
Se observarmos 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 store [Vuex] foi incorporado à página. Isso permitirá que o cliente, ao ser executado após o recebimento da página, reconstitua um novo store [Vuex] com 25 como valor inicial do contador;
Após o recebimento e a exibição da página recebida do servidor, o cliente assume o controle 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]')
// somente servidor
if (process.server) {
this.$store.commit('increment', 25)
}
// cliente e servidor
this.value = this.$store.state.counter
console.log('value=', this.value)
},
beforeMount() {
// somente cliente
console.log('[page1 beforeMount]')
},
mounted() {
// somente 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, portanto, que o servidor transmita informações ao cliente durante o carregamento inicial da página, quando esta é solicitada ao servidor. Vale lembrar que, uma vez obtida essa página, o servidor não é mais consultado e o aplicativo funciona como um aplicativo [vue] clássico, no modo SAP.
5.3. A página [page2]
Na página [page2], mostramos outra maneira de fazer com que:
- o servidor inclua 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] é alterado 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 esse código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retornamos uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// esse resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
// log
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() {
// somente cliente
console.log('[page2 beforeMount]')
},
mounted() {
// somente cliente
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 elemento de um objeto retornado pela função [data]. Aqui, 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. Sua função habitual é buscar dados externos. A função [nuxt] garante que a página não seja enviada ao navegador do cliente antes que a função [asyncData] tenha retornado seus dados assíncronos;
- a função [asyncData] recebe como parâmetro o contexto [nuxt]. Esse objeto é muito rico e dá acesso a muitas informações sobre a aplicação [nuxt]. Vamos descobrir isso nas seções a seguir;
- linha 31: implementamos a função [asyncData] com um [Promise] (consulte o documento |Introdução à linguagem ECMASCRIPT 6 por meio de exemplos|). O construtor dessa classe aceita como parâmetro uma função assíncrona que:
- indica sucesso retornando dados com a função [resolve]. O objeto retornado por essa função é automaticamente incluído nas propriedades [data] da página;
- indica uma falha retornando um erro com a função [reject];
- linha 34: simula-se uma função assíncrona com a função [setTimeout]. Essa função retorna o objeto [{ value: 87 }] (linha 36) após um segundo (linha 31) por meio da função [resolve], que sinaliza o sucesso da função [Promise]. O objeto retornado pela função assíncrona é incluído automaticamente nas propriedades [data] da página. E é, portanto, essa propriedade que a linha 7 exibe;
- linha 27: veremos que a função [asyncData] é executada pelo servidor, mas não pelo cliente;
- linha 29: a inicialização da propriedade [value] é feita pelo servidor;
Observação: o objeto [this] não é reconhecido na função [asyncData], pois 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 que o servidor seja acionado. Assim como no início, para a página [index]:
- o servidor executa a página [page2.vue];
- envia a página gerada ao navegador. Ela é exibida;
- os scripts do cliente incorporados na página enviada assumem o controle e executam novamente a página [page2.vue];
- a página exibida é então modificada;
O resultado final é o seguinte:

Desta vez, o valor exibido é, de fato, aquele definido pelo servidor e, visualmente, não se observa a página “pular” devido a uma alteração feita pelo cliente no valor exibido pelo servidor. O que aconteceu desta vez?
O servidor executou a página [page2] a seguir:
<!-- 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 esse código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retornamos uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// esse resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
// log
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() {
// somente cliente
console.log('[page2 beforeMount]')
},
mounted() {
// somente cliente
console.log('[page2 mounted]')
}
}
</script>
Foi a linha 36 que definiu o valor exibido pela linha 7. Portanto, foi isso que o navegador do cliente recebeu. Mais precisamente, ele 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: 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 store [Vuex]. Este foi instanciado a partir do conteúdo da pasta [store] do aplicativo [nuxt-02];
- [data] contém as propriedades criadas pelo servidor por meio da função [asyncData]. Encontramos a propriedade [value : 87] criada pelo servidor. Os scripts do cliente irão integrar essa propriedade às 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 esse código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retornamos uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// esse resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
// log
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() {
// somente cliente
console.log('[page2 beforeMount]')
},
mounted() {
// somente cliente
console.log('[page2 mounted]')
}
}
</script>
- a linha 7 utiliza a propriedade [value]. No entanto, a página não define nenhuma propriedade chamada [value]. Contudo, os scripts do cliente criaram automaticamente essa propriedade por meio do objeto [data: [{ value: 87 }]] recebido do servidor;
Os logs 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, observa-se que as funções do ciclo de vida não são executadas pelo servidor antes do término 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] é modificado 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 esse 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
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// modifica-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() {
// somente cliente
console.log('[page3 beforeMount]')
},
mounted() {
// somente cliente
console.log('[page3 mounted]')
}
}
</script>
- linha 30: a função [fetch] tem um comportamento semelhante ao da função [asyncData]:
- ela é executada antes das funções do ciclo de vida;
- o objeto [this] não é reconhecido nesta função;
- seu funcionamento é assíncrono;
- o ciclo de vida não começa enquanto a função assíncrona não tiver retornado seu resultado;
- o resultado é retornado aqui 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 suas inúmeras propriedades, o objeto [context] possui uma propriedade [store] que representa o store [Vuex] do aplicativo;
- linha 41: artificialmente, sinaliza-se o sucesso do [Promise] após um segundo (cf. documento |Introdução à linguagem ECMASCRIPT 6 por meio 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 [localhost:81/nuxt-02/page3] manualmente para que o servidor seja acionado. Assim como no início, para a página [index]:
- o servidor executa a página [page3.vue];
- envia a página gerada ao navegador. Ela é exibida;
- os scripts do cliente incorporados na página enviada assumem o controle e executam novamente a página [page3.vue];
- a página exibida é então modificada;
O resultado final é o seguinte:

O valor exibido é, de fato, aquele definido pelo servidor e, visualmente, não se percebe que a página “pule” devido a uma alteração feita pelo cliente no valor exibido pelo servidor. O que aconteceu desta vez?
O servidor executou a página [page3] a seguir:
<!-- 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 esse código?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retornamos uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// modifica-se o store
context.store.commit('increment', 28)
// registro
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() {
// somente cliente
console.log('[page3 beforeMount]')
},
mounted() {
// somente cliente
console.log('[page3 mounted]')
}
}
</script>
- linha 45: a função assíncrona [fetch] é a primeira das funções acima a ser executada. Ela recebe como parâmetro um objeto chamado [context], que é o contexto [nuxt] do momento. Entre as inúmeras propriedades desse objeto, a propriedade [context.store] representa o store [Vuex];
- linha 45: na função assíncrona [fetch], o servidor define o contador do store como 28;
- linha 56: quando a função [created] é executada, [nuxt] garante que a função assíncrona [fetch] tenha concluído 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: percebe-se que o valor na página recebida é 28;
- linha 65: na resposta do servidor, percebe-se que o servidor enviou ao cliente o estado [state] do armazenamento [Vuex]. Graças a essa 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]:
<!-- 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 esse código?
console.log('fetch, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retornamos uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// modifica-se o store
context.store.commit('increment', 28)
// registro
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() {
// somente cliente
console.log('[page3 beforeMount]')
},
mounted() {
// somente 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 exibe esse valor. Como é o mesmo valor enviado pelo servidor, não se observa a página “pular” devido a uma alteração;
Além disso, os logs 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, observa-se que as funções do ciclo de vida não são executadas pelo servidor antes do término 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] mostraram dois métodos que utilizam o store [Vuex] para transmitir informações do servidor ao cliente. É possível questionar se eles são equivalentes. Criamos uma página [page4] para verificar isso.
5.5. A página [page4]
Adicionamos uma nova página [page4] ao nosso aplicativo:

5.5.1. O componente [navigation]
O componente [navigation] é modificado 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 há uma função assíncrona
// simula-se com uma espera de 10 segundos
setTimeout(() => {
// sucesso — retorna-se o valor do contador
resolve(52)
}, 10000)
})
// modifica-se o store
this.$store.commit('increment', valeur)
// registro
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() {
// somente cliente
console.log('[page4 beforeMount]')
},
mounted() {
// somente 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 término da função assíncrona;
- linha 36: recupera-se o resultado da função assíncrona, retornado na linha 41 após 10 segundos (linha 42);
- linhas 50-54: no método [created], executado tanto no lado do servidor quanto 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 digita-se manualmente [localhost:81/nuxt-02/page4] para que o servidor seja acionado. Assim como na inicialização da página [index]:
- o servidor executa a página [page4.vue];
- envia a página gerada ao navegador. Ela é exibida;
- os scripts do cliente incorporados na página enviada assumem o controle e executam novamente a página [page4.vue];
- a página exibida é então modificada;
O resultado final é o seguinte:

Ao contrário do esperado, o valor exibido em [2] não é 52. O que aconteceu?
Os logs são os seguintes:

Pode-se observar que, em [1], o registro de conclusão da ação assíncrona não foi exibido. A função [created], que exibe o valor do contador, mostra 0. Tudo isso sugere que [nuxt] não aguardou o término da ação assíncrona.
Se voltarmos ao terminal da função VSCode, que foi usada para iniciar o aplicativo, encontramos os registros da função [3-4]. Percebemos que a função assíncrona foi efetivamente executada no lado do servidor.
No final das contas, a função [beforeCreate] foi executada integralmente no lado do servidor, mas [nuxt] não aguardou o término de sua execução para enviar a página ao navegador do cliente, embora tenha aguardado o término da função [fetch]. Portanto, é esse método que deve ser utilizado se quisermos que o servidor inicialize um store [Vuex].
5.6. Navegação no aplicativo [vue]
Mostramos 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 ocorre: em funcionamento normal, apenas a página [index] é buscada no servidor. Vejamos as três páginas nesse caso:
página [index]

Já explicamos esse resultado no parágrafo sobre links.
Agora, vamos clicar no link [Page 1]:

O valor exibido é 0. Era 25 quando a página foi solicitada pela primeira vez ao servidor, digitando manualmente seu endereço URL. A explicação é simples. O código executado é o seguinte:
created() {
// cliente e servidor
console.log('[page1 created]')
// somente 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 definia o contador para 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 no link [Page 2]:

Desta vez, nenhum valor é exibido e, além disso, aparece um aviso nos logs do console:

- Em [1], descobrimos que a função [asyncData] foi executada pelo cliente. É sempre assim:
- ela é executada pelo servidor se a página for solicitada ao servidor. Nesse caso, ela 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, há uma expressão reativa {{ value }}, embora a página não possua a propriedade [value];
Lembrando o código executado pelo cliente:
asyncData() {
// quem executa esse código?
console.log('asyncData, client=', process.client, 'serveur=', process.server)
// apenas para o servidor
if (process.server) {
// retorna uma promessa
return new Promise(function(resolve, reject) {
// normalmente, aqui teríamos uma função assíncrona — mas não neste caso
// esse resultado será incluído nas propriedades de [data]
resolve({ value: 87 })
})
}
},
- a linha 3 permitiu constatar 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 restante do código que criava a propriedade [value], pois esse código é executado pelo cliente;
Agora, vamos passar para a página [page3]:

- em [2], o valor era 28 quando a página foi fornecida pelo servidor. Aqui, esse não é o caso;
- na [4], vemos que a função [fetch] foi executada no lado do cliente;
Vamos examinar o código executado pelo cliente:
...
fetch(context) {
// quem executa esse 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, temos aqui uma função assíncrona
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// sucesso
resolve()
}, 1000)
}).then(() => {
// modifica-se o store
context.store.commit('increment', 28)
// registro
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. Portanto, a linha 17, que define o contador como 28, não é executada. Ele permanece em zero. É por isso que o cliente exibe 0 em vez de 28;
Passemos agora para a página [page4]. Temos o seguinte resultado:

- em [2], o valor do contador;
- em [3], os logs 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) {
// é executada a função assíncrona
const valeur = await new Promise(function(resolve, reject) {
// normalmente, aqui temos uma função assíncrona
// simulamos com uma espera de 10 segundos
setTimeout(() => {
// sucesso — retorna-se o valor do contador
resolve(52)
}, 10000)
})
// modifica-se o store
this.$store.commit('increment', valeur)
// registro
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 a 26 não são executadas pelo cliente, pois a condição [process.server] da linha 12 é falsa. Portanto, o contador do store [Vuex] tem valor 0 (seu valor inicial no store) e é esse valor que a página exibe;
Podemos nos perguntar o que acontece quando comentamos a condição [if] das linhas 12 e 26. Aqui está a resposta:
- desta vez, o cliente executa as linhas 14 a 25, mas [nuxt] não aguarda o término da função assíncrona (como no caso do servidor) e, portanto, mantém o contador em 0;
- após 10 s, a função assíncrona é concluída e o contador é definido como 52 na linha 23;
- quando se navega novamente para a página [page4], é exibido o valor 52;
5.7. Résumé
A partir de nossos diversos testes, destacamos os seguintes pontos:
- se a página [index] precisar conter dados externos, estes podem ser buscados pelo servidor por meio da função [asyncData];
- se o servidor precisar inicializar um store [Vuex] com dados externos ao carregar a página [index], ele fará isso 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 “salto” causado pelo fato de a página do cliente substituir visualmente a página enviada pelo servidor e exibida inicialmente;
Vamos explorar outros aspectos da função [nuxt] por meio de um novo exemplo.