8. Exemplo [nuxt-05]: persistência do store com um cookie de sessão
Objetivo: gostaríamos que o store [Vuex] não fosse reinicializado a cada solicitação ao servidor. Para isso, vamos utilizar um cookie de sessão:
- o armazenamento será inicializado pelo servidor e colocado por ele em um cookie de sessão;
- o navegador do cliente receberá esse cookie de sessão e o enviará automaticamente a cada nova solicitação ao servidor;
- o servidor poderá então recuperar esse cookie de sessão e trabalhar com o armazenamento que ele contém, um armazenamento atualizado pelo cliente;
8.1. Présentation
O projeto [nuxt-05] é obtido inicialmente por meio da cópia do projeto [nuxt-04]:

Veremos que apenas o arquivo [store / index.js] será alterado.
Para utilizar cookies com o [nuxt], utilizaremos o módulo [cookie-universal-nuxt], que instalamos junto com o [yarn] em um terminal VSCode:

- no [4], digitamos o comando [yarn add cookie-universal-nuxt];
Assim, um novo módulo é adicionado ao arquivo [package.json] do projeto [dvp]:
...
},
"dependencies": {
"@nuxtjs/axios": "^5.3.6",
"bootstrap": "^4.1.3",
"bootstrap-vue": "^2.0.0",
"cookie-universal-nuxt": "^2.0.19",
"nuxt": "^2.0.0"
},
8.2. O arquivo de configuração [nuxt.config.js]
Para que o [nuxt] possa utilizar os cookies do [cookie-universal-nuxt], é necessário declarar esse módulo no arquivo de configuração [nuxt.config.js]:
...
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
...
- na linha 12, o módulo [cookie-universal-nuxt] é adicionado à tabela de módulos [6] do [nuxt];
O arquivo [nuxt.config.js] fica, no final, da seguinte forma:
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: 'Introduction à [nuxt.js]',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: 'ssr routing loading asyncdata middleware plugins store'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
},
/*
** Customize the progress-bar color
*/
loading: { color: '#fff' },
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Documentação: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
},
// diretório do código-fonte
srcDir: 'nuxt-05',
// roteador
router: {
// raiz da aplicação URL
base: '/nuxt-05/'
},
// 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'
},
// ambiente
env: {
maxAge: 60 * 5
}
}
- linha 79: foi adicionada a chave [env] ao arquivo. Essa chave é uma palavra reservada. Os elementos declarados nesse objeto estão disponíveis a partir do objeto [context.env] nos elementos do aplicativo;
- linha 80: o atributo [maxAge] corresponderá ao tempo de vida máximo do cookie de sessão, medido a partir da última vez em que o cookie foi inicializado. Esse tempo é expresso em segundos. Aqui, definimos um tempo de vida de 5 minutos;
8.3. O princípio da persistência do armazenamento
Os cookies trocados entre o cliente e o servidor estão disponíveis em ambos os lados (cliente e servidor) em:
- [context.app.$cookies], onde o objeto [context] está disponível, ou seja, praticamente em todos os lugares;
- [this.$cookies] dentro de uma visualização;
Obtém-se um cookie específico com a expressão [...$cookies.get(‘nom_du_cookie’)]. Define-se o valor de um cookie com a expressão [...$cookies.set(‘nom_du_cookie’, valeur_du_cookie)].
O princípio do cookie de persistência do store será o seguinte:
- quando o servidor inicializar o armazenamento na função [nuxtServerInit], o estado do armazenamento será armazenado em um cookie chamado ‘session’;
- o cookie ‘session’ passará então a fazer parte da resposta HTTP do servidor. Sabe-se que um navegador reenvia ao servidor os cookies que este lhe enviou. Ele faz isso a cada nova solicitação que envia ao servidor. Sabe-se também que o servidor envia o armazenamento dentro da página que envia ao cliente;
- dentro do navegador, o aplicativo cliente recupera o store enviado pelo servidor e, em seguida, executa suas tarefas. Faremos com que, sempre que ele modificar o store, o novo estado deste seja armazenado no cookie “session” registrado pelo navegador;
- se o usuário forçar uma chamada ao servidor, o navegador cliente reenviará automaticamente todos os cookies que o servidor lhe enviou anteriormente, incluindo o cookie chamado “session”;
- quando, após essa chamada, o servidor reinicializar o armazenamento novamente, ele recuperará o cookie chamado “session” e inicializará o estado do armazenamento com o valor deste;
- haverá, portanto, continuidade do store entre o cliente e o servidor;
8.4. Inicialização do store
O store está implementado no arquivo [store / index.js]:
/* eslint-disable no-console */
export const state = () => ({
// contador
counter: 0
})
export const mutations = {
// incremento do contador em um valor [inc]
increment(state, inc) {
state.counter += inc
},
// substituição do state
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
}
}
export const actions = {
async nuxtServerInit(store, context) {
// quem está executando esse código?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// aguardando o término de uma promessa
await new Promise(function(resolve, reject) {
// normalmente, temos aqui uma função assíncrona
// simulamos isso com uma espera de um segundo
setTimeout(() => {
// inicialização da sessão
initStore(store, context)
// sucesso
resolve()
}, 1000)
})
}
}
function initStore(store, context) {
// existe um cookie de sessão na solicitação atual
const cookies = context.app.$cookies
const session = cookies.get('session')
if (!session) {
// não há sessão existente
console.log("nuxtServerInit, initialisation d'une nouvelle session")
// inicializando o armazenamento
store.commit('increment', 77)
} else {
console.log("nuxtServerInit, reprise d'une session existante")
// atualizando o armazenamento com o cookie de sessão
store.commit('replace', session.store)
}
// colocando o store no cookie de sessão
cookies.set('session', { store: store.state }, { path: context.base, maxAge: context.env.maxAge })
// registro
console.log('initStore terminé, store=', store.state)
}
Comentários
- linhas 2-5: o store será composto por um contador;
- linhas 9-11: esse contador poderá ser incrementado;
- linhas 13-17: o estado do store poderá ser inicializado a partir de um novo estado. Essa função serve para mostrar uma possível inicialização do store quando este não se limita apenas ao contador, como neste caso;
- linhas 21-35: a função [nuxtServerInit] não sofreu alterações;
- linha 30: quando o tempo de espera de um segundo se esgota, inicializa-se o register utilizando a função das linhas 38-56;
- linhas 40-41: começamos recuperando o cookie chamado “session”:
- na primeira execução do aplicativo e na primeira solicitação feita ao servidor, esse cookie ainda não existirá. Ele será então criado (linha 53) e enviado ao navegador do cliente;
- na mesma execução do aplicativo e nas solicitações nº 2, 3, ... feitas ao servidor, esse cookie já existirá, pois o navegador do cliente o reenviará a cada nova solicitação feita ao servidor;
- durante uma segunda execução do aplicativo e na primeira solicitação feita ao servidor, esse cookie também pode existir. De fato, ao final da etapa 1, o cookie foi armazenado no navegador com um determinado tempo de vida. Se esse tempo de vida não tiver expirado, o cookie chamado “session” será enviado com a primeira solicitação feita ao servidor
Em resumo, para cada solicitação feita ao servidor: se o cookie “session” já estiver armazenado no navegador do cliente, o servidor o receberá; caso contrário, não o receberá.
- linhas 42-47: se o servidor não receber o cookie de sessão, o store será inicializado pela linha 46;
- em seguida, na linha 53, um cookie chamado “session” será criado e incluído na resposta HTTP do servidor. O valor do cookie é o objeto [{ store: store.state }]. Portanto, é o estado do store — e não o próprio store — que é colocado no cookie de sessão;
- o terceiro parâmetro da função [set] é um objeto de opções:
- [path] indica a qual URL esse cookie deverá ser reenviado. [context.base] é o URL básico do aplicativo [nuxt-05]. Este está definido no arquivo [nuxt.config.js]:
// roteador
router: {
// raiz dos URL do aplicativo
base: '/nuxt-05/'
},
- [maxAge] é o tempo de vida, em segundos, do cookie no navegador. Após esse período, o navegador não o reenvia mais ao servidor. [context.env.maxAge], por sua vez, retorna um valor registrado no arquivo [nuxt.config.js]:
[env] é uma palavra-chave reservada do arquivo de configuração. Aqui, define-se a validade para 5 minutos. Esse tempo é medido a partir da última vez em que o navegador recebeu o cookie de sessão. Após esse período, o cookie não será reenviado ao servidor, que deverá, então, iniciar uma nova sessão;
- linhas 48-50: se o servidor receber o cookie de sessão, o estado do store será inicializado com o objeto [store] do cookie de sessão. Vale lembrar que esse objeto contém o estado salvo do store;
- em seguida, na linha 53, o cookie de sessão será incluído na resposta enviada ao navegador do cliente:
- a função [get] busca o cookie de sessão na solicitação recebida pelo servidor;
- a função [set] insere o cookie de sessão na resposta que o servidor envia ao navegador do cliente;
- em seguida, na linha 53, o cookie de sessão será incluído na resposta enviada ao navegador do cliente:
8.5. Incremento do contador da loja
O incremento do contador na página [index.vue] ocorre da seguinte maneira:
// gerenciamento de eventos
methods: {
incrementCounter() {
console.log('incrementCounter')
// incremento do contador em 1
this.$store.commit('increment', 1)
// alteração do valor exibido
this.value = this.$store.state.counter
// salvamento do valor armazenado no cookie de sessão
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
Do lado do cliente, sempre que o store for alterado, é necessário salvá-lo no cookie de sessão. De fato, o usuário pode solicitar manualmente uma URL a qualquer momento, e, nesse caso, deve ser possível enviar ao servidor um store atualizado. É por isso que, na linha 10, após o incremento do contador do store, salvamos o estado deste no cookie de sessão:
- os cookies estão disponíveis na propriedade [this.$cookies];
- o estado do store [this.$store.state] é salvo no cookie associado à chave [store];
- o caminho do cookie é [context.base]. Em uma visualização, o contexto está disponível em [this.$nuxt.context];
- o tempo de vida do cookie é [context.env.maxAge], disponível aqui na propriedade [this.$nuxt.context.env.maxAge];
8.6. Execução do exemplo [nuxt-05]
Iniciamos o aplicativo [nuxt-05]:

As capturas de tela a seguir foram feitas no navegador Chrome. Solicitamos o URL [http://localhost:81/nuxt-05/]. Não se esqueça do último / após /nuxt-05, caso contrário, você não obterá os resultados esperados:

- em [4], obtivemos o valor inicial do store (77);
Vamos examinar os logs do navegador (F12):

- em [5-6], os logs do servidor;
- em [7], vemos que o servidor inicia uma nova sessão. Isso significa que ele não recebeu nenhum cookie de sessão;
- em [8], inicialização do contador com o valor 77;
- em [9], a página [index] do servidor (9) e a do cliente (10) exibem, de fato, o mesmo valor do contador;
Agora, vamos examinar os cookies recebidos pelo navegador:

- em [1], selecione a aba [Application] e, em seguida, a opção [Cookies] [2]. Entre todos os cookies do seu navegador, selecione aquele do domínio [http://localhost:81];
- em [4], o cookie chamado “session”. Se você não o tiver, atualize a página [F5]: talvez você tenha excedido seu tempo de validade, que é de 5 minutos;
- em [5], o valor do cookie. Embora não seja muito legível devido à codificação dos caracteres { :, é possível distinguir o valor 77 do contador;
- em [6], o URL do cookie: sempre que esse URL for solicitado, o navegador enviará o cookie ao servidor;
- em [7], a hora de validade do cookie. Quando essa hora for ultrapassada, o cookie será excluído do navegador;
Certifique-se de ter esse cookie. Se você não o tiver, atualize a página (F5). Quando a página estiver com o cookie, atualize-a novamente (F5). Os registros ficarão então da seguinte forma:

Desta vez, em [3], o servidor recuperou corretamente o cookie de sessão. Foi o navegador do cliente que o enviou a ele.
Agora, aumente o contador e, de vez em quando, atualize a página atual (F5), seja [index] ou [page1], você deve observar que o contador não volta para 77, como no exemplo [nuxt-04], mas mantém o valor que tinha no navegador do cliente antes da atualização da página:


Os logs do navegador ficam, então, assim:

Observação: para os testes, talvez seja necessário excluir o cookie de sessão [5] armazenado no navegador para iniciar uma nova sessão, inicializada pelo servidor, na próxima solicitação enviada a ele.
Por fim, vamos mostrar a influência da função [incrementCounter] da página [index] sobre o cookie de sessão armazenado no navegador do cliente:
// gerenciamento de eventos
methods: {
incrementCounter() {
console.log('incrementCounter')
// incremento do contador em 1
this.$store.commit('increment', 1)
// alteração do valor exibido
this.value = this.$store.state.counter
// salvamento do valor armazenado no cookie de sessão
this.$cookies.set('session', { store: this.$store.state }, { path: this.$nuxt.context.base, maxAge: this.$nuxt.context.env.maxAge })
}
}
- linha 10: a alteração do contador é refletida no cookie de sessão;
Vamos verificar esse ponto. Partimos da seguinte situação:

- em [4], o contador do cookie de sessão reflete corretamente o valor exibido [1];
Agora, vamos incrementar o contador uma vez: [5]. O cookie de sessão, que era [4], sofre a seguinte alteração:

- para [7]; o contador do cookie de sessão passou, de fato, para 84. Para verificar isso, é preciso atualizar a visualização [8]. Para isso, selecione outra opção do [Storage] ([9]) e, em seguida, selecione novamente a opção [8]. O novo valor do cookie de sessão deverá então aparecer;