4. Exemplo [nuxt-01]: encaminhamento e navegação
Vamos construir uma série de exemplos simples para descobrir, gradualmente, o funcionamento de uma aplicação [nuxt]. Começaremos por portar a aplicação [vuejs-11] do documento |Introdução ao framework VUE.JS através de um exemplo|, para compreender, em primeiro lugar, o que diferencia a organização do código de uma aplicação [nuxt] da de uma aplicação [vue].
4.1. Estrutura do projeto
O projeto [vuejs-11] era um projeto de navegação entre vistas:

A estrutura do código-fonte do projeto [vuejs-11] era a seguinte:

- [main.js] era o script executado no arranque da aplicação [vue];
- [router.js] definia as regras de encaminhamento;
- [App.vue] era a vista estruturante da aplicação. Organizava o layout das diferentes vistas;
- [Component1, Component2, Component3, Layout, Navigation] eram os componentes utilizados nas diferentes vistas da aplicação;
Na migração da aplicação [vue] [1] para uma aplicação [nuxt] [2]:
- os scripts executados no arranque da aplicação devem ser declarados na chave [plugins] do ficheiro [nuxt.config.js]. Além disso, é possível separar os scripts destinados ao servidor [nuxt] dos destinados ao cliente [nuxt];
- a vista [App.vue] deve ser instalada na pasta [layouts] e renomeada para [default.vue];
- os componentes [Component1, Component2, Component3], que são os destinos do encaminhamento, devem ser migrados para a pasta [pages]. Um deles, que serve de página inicial, deve ser renomeado para [index.vue]. Aqui, renomeámos os ficheiros:
- [Component1] --> [index]: apresenta o texto [Home];
- [Component2] --> [page1]: apresenta o texto [Page 1];
- [Component3] --> [page2]: apresenta o texto [Page 2];
[nuxt] utiliza o conteúdo da pasta [pages] para gerar dinamicamente as seguintes rotas:
Consequentemente, o ficheiro [router.js] utilizado no projeto [vue] torna-se desnecessário no projeto [nuxt].
O ficheiro de configuração [nuxt.config.js] será o seguinte:
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: [
// Documentação: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Documentação: https://axios.nuxtjs.org/usage
'@nuxtjs/axios'
],
/*
** 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-01',
// router
router: {
// raiz da aplicação URL
base: '/nuxt-01/'
},
// 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: '0.0.0.0'
}
}
- linha 62: indica-se a pasta que contém o código-fonte do projeto [dvp];
- linha 66: indica-se a raiz da aplicação [dvp] (pode-se definir o que se quiser);
- linha 43: note-se que a biblioteca [bootstrap-vue] é referenciada na configuração;
4.2. Portagem do ficheiro [main.js]
O ficheiro [main.js] do projeto [vuejs-11] era o seguinte:
// importações
import Vue from 'vue'
import App from './App.vue'
// plugins
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);
// bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
// router
import monRouteur from './router'
// configuração
Vue.config.productionTip = false
// instanciação do projeto [App]
new Vue({
name: "app",
// vista principal
render: h => h(App),
// router
router: monRouteur,
}).$mount('#app')
Para além do [imports], o código realiza as seguintes ações:
- linhas 5-11: utilização da biblioteca [bootstrap-vue]. Esta tarefa é agora realizada pelo módulo [bootstrap-vue/nuxt], na linha 43 do ficheiro de configuração [nuxt.config.js];
- linhas 14 e 25: utilização do ficheiro de encaminhamento [router.js]. Esta tarefa é agora realizada automaticamente pela aplicação [nuxt] a partir da estrutura de pastas do ficheiro [pages];
- linhas 20-26: instanciação da vista principal da aplicação. Numa aplicação [nuxt], é a vista [layouts/default.vue] que serve de vista principal;
O ficheiro [main.js] já não tem razão de ser. Se tivesse, teria sido declarado na chave [plugins] da linha 30 do ficheiro de configuração [nuxt.config.js];
4.3. A vista principal [default.vue]

A vista principal [layouts / default.vue] é a seguinte:
<template>
<div class="container">
<b-card>
<!-- uma mensagem -->
<b-alert show variant="success" align="center">
<h4>[nuxt-01] : routage et navigation</h4>
</b-alert>
<!-- a vista atual do encaminhamento -->
<nuxt />
</b-card>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
- Na linha 9, no projeto [vuejs-11], utilizava-se a tag <router-view /> em vez da tag <nuxt /> utilizada aqui. Ambas parecem funcionar. Experimentei as duas sem notar qualquer alteração. Mantive a tag <nuxt />, que é a recomendada. Ela apresenta a vista atual, ou seja, a página de destino do encaminhamento atual;
4.4. Os componentes

Em comparação com o projeto [vuejs-11], os componentes [layout, navigation] não se alteram:
[components / layout.vue]
<!-- disposição das vistas -->
<template>
<!-- linha -->
<div>
<b-row>
<!-- área com duas colunas -->
<b-col v-if="left" cols="2">
<slot name="left" />
</b-col>
<!-- área com dez colunas -->
<b-col v-if="right" cols="10">
<slot name="right" />
</b-col>
</b-row>
</div>
</template>
<script>
export default {
// parâmetros
props: {
left: {
type: Boolean
},
right: {
type: Boolean
}
}
}
</script>
Este componente serve para estruturar as páginas da aplicação em duas colunas:
- linhas 7-9: a coluna da esquerda com 2 colunas Bootstrap;
- linhas 11-13: a coluna da direita com 10 colunas Bootstrap;
[navigation.vue]
<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>
</template>
Este componente apresenta três links de navegação:

Para saber que valor atribuir aos atributos [to] das linhas 4, 7 e 10, é necessário consultar a pasta [pages] [2]:
- a página [index] terá o URL [/];
- a página [page1] terá as páginas URL e [/page1];
- a página [page2] terá as páginas URL e [/page2];
O componente [navigation] também pode ser escrito da seguinte forma:
<template>
<!-- menu Bootstrap com três opções -->
<b-nav vertical>
<nuxt-link to="/" exact exact-active-class="active">
Home
</nuxt-link>
<nuxt-link to="/page1" exact exact-active-class="active">
Page 1
</nuxt-link>
<nuxt-link to="/page2" exact exact-active-class="active">
Page 2
</nuxt-link>
</b-nav>
</template>
A baliza <b-nav-item> é substituída pela baliza <nuxt-link>, que designa um link de roteamento. Na execução, não notei grandes diferenças, nada que pudesse fazer pender a balança para uma baliza em detrimento da outra.
4.5. As páginas

A página [index.vue] apresenta a seguinte visualização:

O código da página é 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
</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
},
// ciclo de vida
beforeCreate(...args) {
console.log('[home beforeCreate]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
},
created(...args) {
console.log('[home created]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
},
beforeMount(...args) {
console.log('[home beforeMount]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
},
mounted(...args) {
console.log('[home mounted]', 'process.server=', process.server,
'process.client=', process.client, "número de argumentos=", args.length)
}
}
</script>
- linha 5: o componente de navegação está posicionado na coluna da esquerda;
- linhas 7-9: é colocado um alerta na coluna da direita;
Na secção <script>, inserimos código nas funções do ciclo de vida da página [beforeCreate, created, beforeMount, beforeMounted]. Queremos saber quais são executadas pelo servidor []nuxt e quais pelo cliente [nuxt]. Recorde-se o seguinte:
- quando uma página é solicitada, seja no arranque da aplicação, como é o caso da página [index], quer manualmente pelo utilizador que atualiza a página no navegador ou digita um URL manualmente, esta é entregue primeiro pelo servidor [nuxt]. Este interpreta o código acima e executa o JavaScript que nele está contido;
- quando a página enviada pelo servidor [nuxt] chega ao navegador, chega com o código do cliente [nuxt]. Este interpreta novamente a página acima;
- através dos registos, pretendemos saber quem faz o quê para compreender melhor este processo;
- linhas 30-31: na função, utiliza-se um objeto global [process] que existe tanto no servidor como no cliente:
- [process.server] é verdadeiro se o código for executado pelo servidor, falso caso contrário;
- [process.client] é verdadeiro se o código for executado pelo cliente, falso caso contrário;
- uma vez que a variável [process] não está declarada no código, é necessário incluir a linha 14 para [eslint]. A linha [16] é necessária porque, caso contrário, [eslint] declara outro tipo de erro devido à variável [process]. A linha 15 é necessária para permitir a utilização de [console] nas funções do ciclo de vida;
- linha 29: queremos também saber se as funções do ciclo de vida recebem argumentos. Vamos descobrir, de facto, que [nuxt] transmite informações a determinadas funções. Queremos saber se as funções do ciclo de vida fazem parte dessas funções;
- repetimos o mesmo código para as quatro funções;
4.6. O ficheiro [nuxt.config.js]
É este ficheiro que controla a execução do projeto [dvp]. Foi descrito na página 33.
4.7. Execução do projeto
Executamos o projeto:

A página apresentada é a seguinte:

Uma vez instalada no navegador, a aplicação [nuxt] torna-se uma aplicação [vue] clássica. Por isso, não iremos comentar o funcionamento do cliente da aplicação [nuxt-01]. Isso já foi feito no projeto [vuejs-11] do documento |Introdução à estrutura VUE.JS através de um exemplo|.
A aplicação [nuxt] difere da aplicação [vue] apenas em dois momentos:
- o arranque inicial da aplicação, que apresenta a página inicial;
- sempre que o utilizador provoca, de alguma forma, a atualização do navegador;
Nestes dois casos:
- a página solicitada é fornecida pelo servidor;
- a página recebida é processada pelo cliente;
Vejamos os registos do arranque da aplicação (F12 no navegador):

- em [1], os registos do servidor (process.server=true). Aparecem precedidos da menção [Nuxt SSR] (SSR= Server Side Rendered);
- em [2], os registos do cliente no navegador (process.client=true);
A partir destes registos, pode-se deduzir que:
- o servidor executa as funções [beforeCreate, created] do ciclo de vida;
- o cliente executa as funções [beforeCreate, created, beforeMount, mounted] do ciclo de vida;
- o servidor processou a página antes do cliente;
- em ambos os casos, nenhuma das funções executadas recebe argumentos;
Agora, vejamos o código-fonte da página recebida (opção [Code source de la page] 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-01/">
....
<link rel="preload" href="/nuxt-01/_nuxt/runtime.js" as="script">
<link rel="preload" href="/nuxt-01/_nuxt/commons.app.js" as="script">
<link rel="preload" href="/nuxt-01/_nuxt/vendors.app.js" as="script">
<link rel="preload" href="/nuxt-01/_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-01] : routage et navigation</h4>
</div>
<div>
<div class="row">
<div class="col-2">
<ul class="nav flex-column">
<li class="nav-item">
<a href="/nuxt-01/" target="_self" class="nav-link active nuxt-link-active">
Home
</a>
</li>
<li class="nav-item">
<a href="/nuxt-01/page1" target="_self" class="nav-link">
Page 1
</a>
</li>
<li class="nav-item">
<a href="/nuxt-01/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
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.__NUXT__ = (function (a, b, c, d, e, f, g, h, i, j) {
return {
layout: "default", data: [{}], error: null, serverRendered: true,
logs: [
{ date: new Date(1574069600078), args: [a, b, c, d, e, f, g, "(repeated 1 times)"], type: h, level: i, tag: j },
{ date: new Date(1574070938091), args: [a, b, c, d, e, f, g], type: h, level: i, tag: j }
]
}
}("[home beforeCreate]", "process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0", "log", 2, ""));
</script>
<script src="/nuxt-01/_nuxt/runtime.js" defer></script>
<script src="/nuxt-01/_nuxt/commons.app.js" defer></script>
<script src="/nuxt-01/_nuxt/vendors.app.js" defer></script>
<script src="/nuxt-01/_nuxt/app.js" defer></script>
</body>
</html>
Comentários
- A primeira coisa que se pode notar é que o código HTML recebido reflete corretamente o que o utilizador vê. Não era esse o caso das aplicações [vue], nas quais o código-fonte apresentado era o código-fonte de um ficheiro HTML praticamente vazio. Era isso que o navegador tinha recebido. Em seguida, o cliente [vue] assumia o controlo e construía a página esperada pelo utilizador. Era então necessário aceder ao separador [inspecteur] das ferramentas de desenvolvimento do navegador (F12) para descobrir o código HTML da página apresentada;
- linhas 57-67: trata-se do script que apresentou os registos marcados com [Nuxt SSR]. Estes registos foram gerados no lado do servidor e os resultados foram incorporados num script incluído na página enviada;
- linhas 68-71: os scripts que constituem o cliente executado no navegador;
Os scripts das linhas 68-71 são executados e transformam a página recebida. Para saber qual a página que acabou por ser apresentada ao utilizador, é necessário aceder ao separador [inspecteur] nas ferramentas de desenvolvimento do navegador (F12):

Ao expandir a tag <html> [3], obtém-se o seguinte conteúdo:
<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-01/">
...
<link rel="preload" href="/nuxt-01/_nuxt/runtime.js" as="script">
<link rel="preload" href="/nuxt-01/_nuxt/commons.app.js" as="script">
<link rel="preload" href="/nuxt-01/_nuxt/vendors.app.js" as="script">
<link rel="preload" href="/nuxt-01/_nuxt/app.js" as="script">
<script charset="utf-8" src="/nuxt-01/_nuxt/pages_index.js"></script>
<script charset="utf-8" src="/nuxt-01/_nuxt/pages_page1.js"></script>
<script charset="utf-8" src="/nuxt-01/_nuxt/pages_page2.js"></script>
</head>
<body>
<div id="__nuxt">
<div id="__layout">
<div class="container">
<div class="card">
<div class="card-body">
<div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-success" align="center">
<h4>[nuxt-01] : routage et navigation</h4>
</div>
<div>
<div class="row">
<div class="col-2">
<ul class="nav flex-column">
<li class="nav-item">
<a href="/nuxt-01/" target="_self" class="nav-link active nuxt-link-active">
Home
</a>
</li>
<li class="nav-item">
<a href="/nuxt-01/page1" target="_self" class="nav-link">
Page 1
</a>
</li>
<li class="nav-item">
<a href="/nuxt-01/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
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
window.__NUXT__ = (function (a, b, c, d, e, f, g, h, i) {
return {
layout: "default", data: [{}], error: null, serverRendered: true,
logs: [
{ date: new Date(1574068674481), args: ["[home beforeCreate]", a, b, c, d, e, f], type: g, level: h, tag: i },
{ date: new Date(1574068674482), args: ["[home created]", a, b, c, d, e, f], type: g, level: h, tag: i }
]
}
}("process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0", "log", 2, ""));
</script>
<script src="/nuxt-01/_nuxt/runtime.js" defer=""></script>
<script src="/nuxt-01/_nuxt/commons.app.js" defer=""></script>
<script src="/nuxt-01/_nuxt/vendors.app.js" defer=""></script>
<script src="/nuxt-01/_nuxt/app.js" defer=""></script>
<iframe id="mc-sidebar-container" ...></iframe>
<iframe id="mc-topbar-container"...> </iframe>
<iframe id="mc-toast-container" ...></iframe>
<iframe id="mc-download-overlay-container"...></iframe>
</body>
Comentários
- à primeira vista, a página apresentada nas linhas 19-59 parece ser a mesma que a página recebida;
- linhas 14-16: surgem três novos scripts, um para cada uma das páginas da aplicação;
- linhas 76-79: surgem quatro [iframe];
Nas linhas 33, 37 e 42, os links apresentam um problema. Parecem ser links normais que, quando clicados, enviam uma solicitação ao servidor. No entanto, durante a execução, verifica-se que isso não é verdade: não há qualquer solicitação enviada ao servidor. Para compreender porquê, é necessário voltar ao separador [inspecteur] do navegador:

Vemos que, em [1, 2], foram associados eventos aos links. Foram os scripts das linhas 71-74 que associaram os manipuladores de eventos aos links. Portanto:
- a página apresentada ao cliente é visualmente idêntica à enviada pelo servidor;
- foi adicionado um comportamento dinâmico à página pelo cliente;
Agora, vamos solicitar a página [page1] digitando manualmente URL em vez de [http://192.168.1.128:81/nuxt-01/page1]. Os registos passam a ser os seguintes:

Obtenemos os mesmos resultados que para a página [index], mas para a [page1]. O código-fonte da página recebida é o seguinte:
<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-01] : routage et navigation</h4></div> <div>
<div class="row">
<div class="col-2">
<ul class="nav flex-column">
<li class="nav-item">
<a href="/nuxt-01/" target="_self" class="nav-link">
Home
</a>
</li>
<li class="nav-item">
<a href="/nuxt-01/page1" target="_self" class="nav-link active nuxt-link-active">
Page 1
</a>
</li>
<li class="nav-item">
<a href="/nuxt-01/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
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>window.__NUXT__ = { layout: "default", data: [{}], error: null, serverRendered: true, logs: [{ date: new Date(1573917721122), args: ["[page1 beforeCreate]", "process.server=", "true", "process.client=", "false", "nombre d'arguments=", "0"], type: "log", level: 2, tag: "" }] };</script>
<script src="/nuxt-01/_nuxt/runtime.js" defer></script>
<script src="/nuxt-01/_nuxt/commons.app.js" defer></script>
<script src="/nuxt-01/_nuxt/vendors.app.js" defer></script>
<script src="/nuxt-01/_nuxt/app.js" defer></script>
</body>
Obtém-se o mesmo tipo de página que a página [index], mas com o alerta da vista [Page 1] (linha 30). Nas linhas 41-44, o código do cliente foi devolvido juntamente com a página. Em suma, solicitar manualmente uma URL equivale a reiniciar a aplicação. Apenas a página apresentada não é necessariamente a página inicial, mas sim aquela que foi solicitada. Assim que a página é recebida, é o cliente que assume o controlo. O servidor não será mais solicitado, a menos que o utilizador decida o contrário.