Skip to content

4. Exemplo [nuxt-01]: roteamento e navegação

Vamos construir uma série de exemplos simples para descobrir, passo a passo, o funcionamento de um aplicativo [nuxt]. Começaremos por portar o aplicativo [vuejs-11] do documento |Introdução à estrutura VUE.JS por meio de um exemplo|, para descobrir, em primeiro lugar, o que diferencia a organização do código de uma aplicação [nuxt] daquela de uma aplicação [vue].

4.1. Estrutura do projeto

O projeto [vuejs-11] era um projeto de navegação entre visualizações:

Image

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

Image

  • [main.js] era o script executado ao iniciar o aplicativo [vue];
  • [router.js] definia as regras de roteamento;
  • [App.vue] era a vista estruturante do aplicativo. Ela organizava o layout das diferentes vistas;
  • [Component1, Component2, Component3, Layout, Navigation] eram os componentes utilizados nas diferentes visualizações do aplicativo;

Na migração da aplicação [vue] [1] para uma aplicação [nuxt] [2]:

  • os scripts executados na inicialização do aplicativo devem ser declarados na chave [plugins] do arquivo [nuxt.config.js]. Além disso, é possível separar os scripts destinados ao servidor [nuxt] daqueles destinados ao cliente [nuxt];
  • a vista [App.vue] deve ser instalada na pasta [layouts] e renomeada como [default.vue];
  • os componentes [Component1, Component2, Component3], que são os destinos do roteamento, devem ser migrados para a pasta [pages]. Um deles, que serve como página inicial, deve ser renomeado para [index.vue]. Aqui, renomeamos os arquivos:
    • [Component1] --> [index]: exibe o texto [Home];
    • [Component2] --> [page1]: exibe o texto [Page 1];
    • [Component3] --> [page2]: exibe o texto [Page 2];

[nuxt] utiliza o conteúdo da pasta [pages] para gerar dinamicamente as seguintes rotas:

1
2
3
{ name :’index’, ‘path’ :’/’}
{ name :’page1’, ‘path’ :’/page1’}
{ name :’page2’, ‘path’ :’/page2’}

Consequentemente, o arquivo [router.js] utilizado no projeto [vue] torna-se desnecessário no projeto [nuxt].

O arquivo 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',
  // roteador
  router: {
    // raiz da aplicação URL
    base: '/nuxt-01/'
  },
  // 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: '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: observe que a biblioteca [bootstrap-vue] está referenciada na configuração;

4.2. Portagem do arquivo [main.js]

O arquivo [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'

// roteador
import monRouteur from './router'

// configuração
Vue.config.productionTip = false

// instanciação do projeto [App]
new Vue({
  name: "app",
  // visualização principal
  render: h => h(App),
  // roteador
  router: monRouteur,
}).$mount('#app')

Além do [imports], o código realiza as seguintes ações:

  • linhas 5-11: uso da biblioteca [bootstrap-vue]. Essa tarefa agora é realizada pelo módulo [bootstrap-vue/nuxt], na linha 43 do arquivo de configuração [nuxt.config.js];
  • linhas 14 e 25: uso do arquivo de roteamento [router.js]. Essa tarefa agora é realizada automaticamente pelo aplicativo [nuxt] a partir da árvore de pastas do arquivo [pages];
  • linhas 20-26: instanciamento da visualização principal do aplicativo. Em um aplicativo [nuxt], é a visualização [layouts/default.vue] que serve como visualização principal;

O arquivo [main.js] não tem mais razão de ser. Se tivesse, ele teria sido declarado na chave [plugins] da linha 30 do arquivo de configuração [nuxt.config.js];

4.3. A visualização principal [default.vue]

Image

A visualização 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 visualização atual do roteamento -->
      <nuxt />
    </b-card>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
  • Na linha 9, no projeto [vuejs-11], havia a tag <router-view /> em vez da tag <nuxt /> usada aqui. Ambas parecem funcionar. Testei as duas, mas não notei nenhuma mudança. Mantive a tag <nuxt />, que é a recomendada. Ela exibe a visualização atual, ou seja, a página de destino do roteamento atual;

4.4. Os componentes

Image

Em relação ao projeto [vuejs-11], os componentes [layout, navigation] não sofrem alterações:

[components / layout.vue]


<!-- disposição das visualizações -->
<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 do aplicativo em duas colunas:

  • linhas 7-9: a coluna da esquerda com 2 colunas do Bootstrap;
  • linhas 11-13: a coluna da direita com 10 colunas do 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 exibe três links de navegação:

Image

Para saber quais valores atribuir aos atributos [to] das linhas 4, 7 e 10, é preciso 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 tag <b-nav-item> é substituída pela tag <nuxt-link>, que designa um link de roteamento. Na execução, não notei grande diferença, nada que pudesse fazer com que uma tag fosse preferida em detrimento da outra.

4.5. As páginas

Image

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

Image

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: um alerta é colocado na coluna da direita;

Na seçã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]. Vale lembrar duas coisas:

  • quando uma página é solicitada, seja no início da aplicação, como no caso da página [index], seja manualmente pelo usuário que atualiza a página no navegador ou digita um URL manualmente, ela é entregue primeiro pelo servidor [nuxt]. Este interpreta o código acima e executa o JavaScript nele contido;
  • quando a página enviada pelo servidor [nuxt] chega ao navegador, ela vem acompanhada do código do cliente [nuxt]. Este interpreta novamente a página acima;
  • por meio dos logs, queremos saber quem faz o quê para entender melhor esse processo;
  • linhas 30-31: na função, utiliza-se um objeto global [process] que existe tanto no servidor quanto 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;
    • como 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 o uso de [console] nas funções do ciclo de vida;
  • linha 29: queremos saber também se as funções do ciclo de vida recebem argumentos. Vamos descobrir, de fato, que [nuxt] transmite informações para certas funções. Queremos saber se as funções do ciclo de vida fazem parte delas;
  • repetimos o mesmo código para as quatro funções;

4.6. O arquivo [nuxt.config.js]

É ele que controla a execução do projeto [dvp]. Ele foi descrito na página 33.

4.7. Execução do projeto

Executamos o projeto:

Image

A página exibida é a seguinte:

Image

Uma vez instalada no navegador, a aplicação [nuxt] torna-se uma aplicação [vue] clássica. Portanto, não comentaremos o funcionamento do lado do cliente do aplicativo [nuxt-01]. Isso já foi feito no projeto [vuejs-11] do documento |Introdução à estrutura VUE.JS por meio de um exemplo|.

O aplicativo [nuxt] difere do aplicativo [vue] apenas em dois momentos:

  • o início inicial do aplicativo, que exibe a página inicial;
  • sempre que o usuário, de alguma forma, provoca a atualização do navegador;

Nesses dois casos:

  • a página solicitada é fornecida pelo servidor;
  • a página recebida é processada pelo cliente;

Vamos examinar os logs de inicialização do aplicativo (F12 no navegador):

Image

  • em [1], os logs do servidor (process.server=true). Eles aparecem precedidos pela menção [Nuxt SSR] (SSR= Server Side Rendered);
  • em [2], os logs do cliente no navegador (process.client=true);

A partir desses logs, 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, vamos examinar 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 usuário vê. Esse não era o caso dos aplicativos [vue], nos quais o código-fonte exibido era o código-fonte de um arquivo HTML praticamente vazio. Era isso que o navegador havia recebido. Em seguida, o cliente [vue] assumia o controle e construía a página esperada pelo usuário. Era então necessário acessar a aba [inspecteur] das ferramentas de desenvolvimento do navegador (F12) para descobrir o código HTML da página exibida;
  • linhas 57-67: esse é o script que exibiu os logs marcados com [Nuxt SSR]. Esses logs foram gerados no lado do servidor e os resultados foram incorporados a um script incluído na página enviada;
  • linhas 68-71: os scripts que compõem o cliente executado no navegador;

Os scripts das linhas 68-71 são executados e transformam a página recebida. Para saber qual página foi exibida para o usuário, é preciso acessar a aba [inspecteur] nas ferramentas de desenvolvimento do navegador (F12):

Image

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 exibida nas linhas 19 a 59 parece ser a mesma que a página recebida;
  • linhas 14 a 16: aparecem três novos scripts, um para cada uma das páginas do aplicativo;
  • linhas 76 a 79: aparecem quatro [iframe];

Nas linhas 33, 37 e 42, os links apresentam um problema. Eles parecem ser links normais que, quando clicados, enviam uma solicitação ao servidor. No entanto, na execução, percebe-se que isso não é verdade: não há solicitação ao servidor. Para entender o motivo, é preciso voltar à aba [inspecteur] do navegador:

Image

Percebe-se que, em [1, 2], foram associados eventos aos links. Foram os scripts das linhas 71 a 74 que associaram manipuladores de eventos aos links. Portanto:

  • a página exibida pelo cliente é visualmente idêntica àquela enviada pelo servidor;
  • um comportamento dinâmico foi adicionado à 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 logs ficam da seguinte forma:

Image

Obtemos 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 visualização [Page 1] (linha 30). Linhas 41-44: o código do cliente foi enviado junto com a página. No fim das contas, solicitar manualmente uma URL é o mesmo que reiniciar o aplicativo. Apenas a página exibida não é necessariamente a página inicial, mas sim aquela que foi solicitada. Uma vez recebida a página, o cliente assume o controle. O servidor não será mais consultado, a menos que o usuário decida o contrário.