16. Exemplo [nuxt-13]: verificação da navegação de [nuxt-12]
Neste exemplo, estamos interessados na navegação do [nuxt-12]. Não fizemos isso no [nuxt-12] porque a verificação da navegação teria complicado ainda mais um exemplo que já era complexo.
Objetivo: queremos que o usuário só possa realizar ações autorizadas:
- se a sessão jSON não tiver sido iniciada, então apenas a sessão URL e [/] são autorizadas;
- se a sessão jSON tiver sido iniciada, mas o usuário não estiver autenticado, então apenas as sessões URL e [/authentification] serão autorizadas;
- se a sessão jSON tiver sido iniciada e o usuário estiver autenticado, então apenas as sessões URL e [/get-admindata, /fin-session] são autorizadas;
- quando o destino do roteamento atual não for autorizado, será realizada uma redirecionamento para uma URL autorizada;
O exemplo [nuxt-13] é obtido inicialmente por cópia do exemplo [nuxt-12]:

É na pasta de roteamento [middleware] que as modificações serão realizadas.
16.1. Roteamento do aplicativo [nuxt]
O roteamento da aplicação está configurado da seguinte forma no arquivo [nuxt.config]:
// roteador
router: {
// raiz dos URL do aplicativo
base: '/nuxt-13/',
// middleware de roteamento
middleware: ['routing']
},
- linha 6: o roteamento da aplicação é controlado pelo arquivo [middleware/routing];
O arquivo [middleware/routing] é o seguinte:
/* eslint-disable no-console */
// importamos os middlewares do servidor e do cliente
import serverRouting from './server/routing'
import clientRouting from './client/routing'
export default function(context) {
// quem executa esse código?
console.log('[middleware], process.server', process.server, ', process.client=', process.client)
if (process.server) {
// roteamento do servidor
serverRouting(context)
} else {
// roteamento do cliente
clientRouting(context)
}
}
- linhas 10-16: o roteamento do cliente e do servidor [nuxt] é tratado de maneira diferente. Esse é um ponto em que eles diferem significativamente;
- linha 4: o roteamento do servidor é implementado pelo script [middleware/server/routing];
- linha 5: o roteamento do cliente é implementado pelo script [middleware/client/routing];
16.2. Roteamento do cliente [nuxt]
O roteamento do cliente [nuxt] permanece igual ao que era no [nuxt-12]:
/* eslint-disable no-console */
export default function(context) {
// quem executa esse código?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// gerenciamento do cookie de sessão PHP no navegador
// o cookie de sessão PHP do navegador deve ser idêntico ao encontrado na sessão do Nuxt
// a ação [fin-session] recebe um novo cookie PHP (tanto no servidor quanto no cliente Nuxt)
// se for o servidor que o receber, o cliente deve transmiti-lo ao navegador
// para suas próprias comunicações com o servidor PHP
// estamos aqui em um roteamento do cliente
// recuperamos o cookie da sessão PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// se existir, atribuímos o cookie de sessão PHP ao navegador
document.cookie = phpSessionCookie
}
...
}
Para evitar que o cliente acesse rotas não autorizadas, basta oferecer a ele, no menu de navegação do cliente, apenas as rotas autorizadas. O componente [components/navigation] passa a ser o seguinte:
<template>
<!-- menu Bootstrap com três opções -->
<b-nav vertical>
<b-nav-item v-if="$store.state.jsonSessionStarted && !$store.state.userAuthenticated" to="/authentification" exact exact-active-class="active">
Authentification
</b-nav-item>
<b-nav-item
v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated && !$store.state.adminData"
to="/get-admindata"
exact
exact-active-class="active"
>
Requête AdminData
</b-nav-item>
<b-nav-item v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated" to="/fin-session" exact exact-active-class="active">
Fin session impôt
</b-nav-item>
</b-nav>
</template>
- linha 4: a opção [Authentification] só é oferecida se a sessão jSON tiver sido iniciada, mas o usuário não estiver autenticado. Se a sessão jSON não tiver sido iniciada ou se o usuário já estiver autenticado, a opção não será oferecida;
- linhas 7-11: a opção [Requête AdminData] só é oferecida se a sessão jSON tiver sido iniciada, se o usuário estiver autenticado e se o dado [AdminData] ainda não tiver sido recuperado. Se uma dessas três condições não for satisfeita (sessão jSON não iniciada, usuário não autenticado ou os dados [AdminData] já recuperados), a opção não é oferecida;
- linha 15: a opção [Fin session impôt] é oferecida assim que a sessão jSON for iniciada e o usuário estiver autenticado; caso contrário, ela não é oferecida;
16.3. Roteamento do servidor [nuxt]
O roteamento do servidor é, em geral, mais complexo do que o do cliente, pois o usuário pode digitar qualquer URL na barra de endereços do navegador. Podemos deixar isso acontecer (afinal, o usuário não deveria fazer isso) ou tentar controlar a situação. É isso que faremos aqui, a título de exemplo, pois, no caso do aplicativo [nuxt-12], podemos muito bem dispensar esse controle, já que o servidor de cálculo de impostos está bem protegido contra esses URL inseridos manualmente e sabe enviar as mensagens de erro adequadas. Vimos isso no [next-12], onde não havia nenhum controle de roteamento.
O roteamento de um servidor [nuxt] é muito diferente do de um cliente [nuxt] no que diz respeito ao conceito de redirecionamento:
- quando um servidor [nuxt] é redirecionado, ele envia uma ordem de redirecionamento ao navegador do cliente com o destino do redirecionamento. O navegador, então, faz uma nova solicitação ao servidor [nuxt], solicitando o destino que lhe foi transmitido. Tudo ocorre como se o usuário tivesse digitado manualmente o endereço URL do destino do redirecionamento: todo o aplicativo [nuxt] é reiniciado e, consequentemente, todo o seu ciclo de vida (plug-ins do servidor, store, roteamento do servidor, páginas);
- quando um cliente [nuxt] é redirecionado, nada disso ocorre. Há uma simples mudança de página, a mesma que teria ocorrido se o usuário tivesse clicado em um link que levasse ao destino do redirecionamento. O ciclo de vida é, então, diferente (roteamento do cliente, exibição do destino da rota);
Por esse motivo, é preferível separar o roteamento do cliente do roteamento do servidor, mesmo que os dois códigos possam parecer semelhantes.
O script de roteamento do servidor [middleware/server/routing] será o seguinte:
/* eslint-disable no-console */
export default function(context) {
// quem está executando esse código?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// recuperamos algumas informações do store [nuxt]
const store = context.store
// de onde viemos?
const from = store.state.from || 'nowhere'
...
}
- no roteamento do cliente, a função de roteamento recebe o contexto [context] com a propriedade [context.from], que é a rota da página de onde se vem. A rota para onde se vai é obtida por [context.route];
- no roteamento do servidor, a função de roteamento recebe o contexto [context] sem a propriedade [context.from]. O roteamento do servidor só intervém quando um URL é solicitado manualmente ao servidor [nuxt]. Sabe-se que, nesse caso, toda a aplicação [nuxt] é reinicializada. É como se começássemos do zero e, portanto, não há o conceito de “página anterior”;
- graças à sessão [nuxt], sabemos que o servidor pode recuperar essa sessão e, assim, não precisar recomeçar do zero. Portanto, é nessa sessão [nuxt] e, mais especificamente, no armazenamento dessa sessão, que armazenaremos o nome da última página exibida pelo navegador do cliente antes que uma URL seja solicitada ao servidor [nuxt];
- linhas 7-9: recuperamos o nome da última página exibida pelo navegador do cliente. Ao iniciar o aplicativo, essa informação [from] não existe no armazenamento. Em seguida, atribui-se o nome [nowhere] à variável [from];
Para que o servidor [nuxt] possa recuperar no armazenamento o nome da última página exibida pelo navegador do cliente, é necessário que o cliente [nuxt] também insira essa informação no armazenamento. O script de roteamento do cliente [nuxt] é, portanto, completado da seguinte forma:
/* eslint-disable no-console */
export default function(context) {
// quem está executando esse código?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// Gerenciamento do cookie de sessão PHP no navegador
// o cookie de sessão PHP do navegador deve ser idêntico ao encontrado na sessão do Nuxt
// a ação [fin-session] recebe um novo cookie PHP (tanto no servidor quanto no cliente Nuxt)
// se for o servidor que o receber, o cliente deve transmiti-lo ao navegador
// para suas próprias comunicações com o servidor PHP
// estamos aqui em um roteamento do cliente
// recuperamos o cookie da sessão PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// se existir, atribuímos o cookie de sessão PHP ao navegador
document.cookie = phpSessionCookie
}
// coloca-se na sessão o nome da página para a qual se está indo — sem redirecionamento do servidor
context.store.commit('replace', { serverRedirection: false, from: context.route.name })
// salva-se o store na sessão [nuxt]
const session = context.app.$session()
session.value.store = context.store.state
session.save(context)
}
- as linhas 19 a 24 são adicionadas;
- linha 20: insere-se no store o nome da página [context.route.name] que será exibida e que, portanto, no próximo roteamento, será a página de onde viemos. Além disso, veremos que, no roteamento do servidor [nuxt], este precisa saber se o roteamento atual é resultado de um redirecionamento anterior do servidor [nuxt]. Aqui, esse não é o caso e, portanto, definimos a propriedade [serverRedirection] como [false];
- linhas 22-24: o estado do store é armazenado na sessão [nuxt] (linha 23) e, em seguida, a sessão [nuxt] é salva em um cookie (linha 24), que, por sua vez, será armazenado no navegador do cliente [nuxt];
Voltemos ao script de roteamento do servidor [nuxt]:
/* eslint-disable no-console */
export default function(context) {
// quem executa esse código?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// recuperamos algumas informações do store [nuxt]
const store = context.store
// de onde viemos?
const from = store.state.from || 'nowhere'
// Para onde estamos indo?
const to = context.route.name
// possível redirecionamento
let redirection = ''
// gerenciamento do roteamento concluído
let done = false
// já estamos em um redirecionamento do servidor [nuxt]?
if (store.state.serverRedirection) {
// nada a fazer
done = true
}
// Trata-se de uma atualização da página?
if (to === from) {
// não há nada a fazer
done = true
}
// controle da navegação do servidor [nuxt]
// baseia-se na navegação do cliente no componente [navigation]
// caso em que a sessão PHP não tenha sido iniciada
if (!done && !store.state.jsonSessionStarted && to !== 'index') {
// redirecionamento
redirection = 'index'
// trabalho concluído
done = true
}
// caso em que o usuário não esteja autenticado
if (!done && store.state.jsonSessionStarted && !store.state.userAuthenticated && to !== 'authentification') {
// redirecionamento
redirection = from
// trabalho concluído
done = true
}
// caso em que o usuário foi autenticado
if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && to !== 'get-admindata' && to !== 'fin-session') {
// permanece-se na mesma página
redirection = from
// trabalho concluído
done = true
}
// caso em que [adminData] foi obtido
if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && store.state.adminData && to !== 'fin-session') {
// permanece-se na mesma página
redirection = from
// trabalho concluído
done = true
}
// foram realizadas todas as verificações ---------------------
// redirecionamento?
if (redirection) {
// registramos o redirecionamento no store
store.commit('replace', { serverRedirection: true })
} else {
// sem redirecionamento
store.commit('replace', { serverRedirection: false, from: to })
}
// estamos salvando o store na sessão [nuxt]
const session = context.app.$session()
session.value.store = store.state
session.save(context)
// é feita a eventual redirecionamento
if (redirection) {
context.redirect({ name: redirection })
}
}
- linhas 6-9: recupera-se o valor de [from] no armazenamento do servidor [nuxt];
- linha 11: registra-se o destino do roteamento atual;
- linha 13: o roteamento pode levar a um redirecionamento do navegador do cliente. [redirection] será o destino desse redirecionamento;
- linha 15: [done] para [true] indica que o roteamento foi concluído;
- linhas 17-21: verifica-se primeiro se o roteamento atual decorre de uma solicitação de redirecionamento enviada ao navegador do cliente. Essa informação está armazenada na propriedade [serverRedirection] do store. Se essa propriedade estiver definida como verdadeiro, isso significa que o servidor [nuxt] enviou um redirecionamento ao navegador do cliente durante a solicitação anterior ao servidor [nuxt]. Nesse caso, não há roteamento a ser feito. Na solicitação anterior, o roteador do servidor [nuxt] decidiu que o navegador do cliente deveria ser redirecionado. Essa decisão não precisa ser questionada por um novo roteamento;
- linhas 23-27: verifica-se se o roteamento em andamento é uma recarga da página. Se for, deixa-se que ocorra;
- a partir da linha 29, retomam-se as regras aplicadas no componente [navigation] do cliente [nuxt] (ver parágrafo anterior);
- linhas 32-38: trata-se do caso em que a sessão jSON não foi iniciada e o destino do roteamento não é a página [index]. Nesse caso, redireciona-se o navegador do cliente para a página [index];
- linhas 40-46: trata-se do caso em que a sessão jSON foi iniciada, o usuário não está autenticado e o destino do roteamento atual não é a página [authentification]. Nesse caso, o roteamento é recusado e permanecemos onde estávamos;
- linhas 48-54: tratamos o caso em que a sessão jSON foi iniciada, o usuário está autenticado e o destino do roteamento atual não é nem a página [get-admindata], nem a página [fin-session], que são, então, os únicos destinos possíveis. Nesse caso, o roteamento solicitado é recusado e retorna-se ao ponto anterior;
- linhas 56-62: trata-se do caso em que [adminData] foi obtido. Nesse caso, há apenas um destino possível para o roteamento: a página [fin-session]. Se não fosse essa a página solicitada, o roteamento é recusado e voltamos ao ponto anterior;
- linhas 64-72: se houve redirecionamento, registra-se no armazenamento do servidor [nuxt]: [serverRedirection: true]. Observe-se que não se atribui nenhum valor à propriedade [from] do armazenamento. A razão para isso é que haverá redirecionamento do navegador do cliente e vimos que, nesse caso, não há roteamento (linhas 17-20) e a propriedade [from] do store não é utilizada;
- linhas 66-69: se não houver redirecionamento, isso também é registrado no armazenamento do servidor [nuxt]: [serverRedirection: false]. Além disso, o roteamento em andamento exibirá a página [to], que, para a próxima solicitação (cliente ou servidor [nuxt]), se tornará a página anterior. É por isso que se grava [from: to];
- linhas 73-76: salvamos o store na sessão [nuxt], que por sua vez é salva em um cookie;
- linhas 77-80: se [redirection] não estiver vazio, solicita-se ao navegador que se redirecione. Caso contrário (o que não é mostrado aqui), o ciclo de vida do servidor [nuxt] continuará: a página [to] será processada pelo servidor [nuxt] e enviada ao navegador do cliente [nuxt] com o cookie de sessão [nuxt];
O roteamento escolhido aqui para o servidor [nuxt] é arbitrário. Poderíamos ter escolhido outro ou, como já foi dito, não ter feito nenhum. O escolhido acima tem a vantagem de sempre manter o aplicativo em um estado estável, independentemente da página URL solicitada pelo usuário.
É possível melhorar um aspecto quando a página carregada no final é a página original. Há dois casos:
- o usuário provocou uma recarga da página (to===from);
- há redirecionamentos para a página original (redirecionamento===from);
Em ambos os casos, a página original será executada novamente com sua chamada assíncrona ao servidor de cálculo do imposto. Vejamos um exemplo. Se, após a autenticação, o usuário recarregar a página (F5). Nesse caso, no roteamento acima, temos: [to]=[from]=[authentification]. Não há redirecionamento. A página [to=authentification] será executada pelo servidor [nuxt]. Se nada for feito, a função [asyncData] será executada novamente. Isso é desnecessário, já que a autenticação já foi realizada.
É possível melhorar a situação modificando ligeiramente a página [authentification]:
// dados assíncronos
async asyncData(context) {
// registro
console.log('[authentification asyncData started]')
// não se repete a operação se a página já tiver sido solicitada
if (process.server && context.store.state.userAuthenticated) {
console.log('[authentification asyncData canceled]')
return { result: '[succès]' }
}
// cliente [nuxt]
if (process.client) {
// início da espera
context.app.$eventBus().$emit('loading', true)
// sem erros
context.app.$eventBus().$emit('errorLoading', false)
}
try {
// autenticação no servidor
...
- linhas 6-9: se a página for executada pelo servidor [nuxt] e for constatado no store que a autenticação já foi feita, então retorna-se diretamente o resultado desejado (linha 8);
Fazemos o mesmo para todas as páginas:
Página [index]:
// dados assíncronos
async asyncData(context) {
// registro
console.log('[index asyncData started]')
// não se faz a mesma coisa duas vezes se a página já tiver sido solicitada
if (process.server && context.store.state.jsonSessionStarted) {
console.log('[index asyncData canceled]')
return { result: '[succès]' }
}
try {
...
Página [get-admindata]
// dados assíncronos
async asyncData(context) {
// log
console.log('[get-admindata asyncData started]')
// não se faz a mesma coisa duas vezes se a página já tiver sido solicitada
if (process.server && context.store.state.adminData) {
console.log('[get-admindata asyncData canceled]')
return { result: context.store.state.adminData }
}
// cliente
if (process.client) {
// início da espera
context.app.$eventBus().$emit('loading', true)
// sem erro
context.app.$eventBus().$emit('errorLoading', false)
}
try {
...
Página [fin-session]
// dados assíncronos
async asyncData(context) {
// registro
console.log('[fin-session asyncData started]')
// não se faz as coisas duas vezes se a página já tiver sido solicitada
if (process.server && context.store.state.jsonSessionStarted && !context.store.state.userAuthenticated) {
console.log('[fin-session asyncData canceled]')
return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
}
// caso do cliente [nuxt]
if (process.client) {
// início da espera
context.app.$eventBus().$emit('loading', true)
// sem erros
context.app.$eventBus().$emit('errorLoading', false)
}
try {
16.4. Exécution
Para executar este exemplo, é necessário, antes da execução, excluir o cookie de sessão [nuxt] e o cookie PHP do navegador que está executando o cliente [nuxt], a fim de iniciar a partir de uma situação limpa. Abaixo, um exemplo com o navegador Chrome:

16.5. Conclusion
O roteamento do servidor [nuxt] é complexo, pois é necessário prever todos os URL que o usuário possa digitar manualmente. Trata-se de um caso clássico. Um aplicativo [nuxt] não se destina a ser usado dessa forma. Uma vez que a página [index] seja servida pelo roteador do servidor [nuxt], seria possível redirecionar as chamadas subsequentes feitas ao servidor para uma página de erro.
No caso específico do nosso exemplo [nuxt-13], o roteamento do servidor [nuxt] era desnecessário. O roteamento padrão (na verdade, a ausência de roteamento) no exemplo [nuxt-12] era perfeitamente adequado.