Criando aplicativos Vue.js renderizados no lado do servidor usando Nuxt.js
Publicados: 2022-03-11Estruturas/bibliotecas JavaScript, como o Vue, podem oferecer uma experiência fantástica ao usuário ao navegar em seu site. A maioria oferece uma maneira de alterar dinamicamente o conteúdo da página sem precisar enviar uma solicitação ao servidor a cada vez.
No entanto, há um problema com essa abordagem. Ao carregar inicialmente seu site, seu navegador não recebe uma página completa para exibir. Em vez disso, ele recebe um monte de peças para construir a página (HTML, CSS, outros arquivos) e instruções sobre como juntá-los (um framework/biblioteca JavaScript) Leva um tempo mensurável para reunir todas essas informações antes que seu navegador realmente tenha algo para exibir. É como receber um monte de livros junto com uma estante compacta. Você teria que construir a estante primeiro e depois preenchê-la com os livros.
A solução para isso é inteligente: tenha uma versão do framework/biblioteca no servidor que possa construir uma página pronta para exibição. Em seguida, envie esta página completa para o navegador junto com a capacidade de fazer mais alterações e ainda ter conteúdo de página dinâmico (o framework/biblioteca), assim como enviar uma estante pronta junto com alguns livros. Claro, você ainda tem que colocar os livros na estante, mas você tem algo utilizável imediatamente.
Além da analogia boba, há também várias outras vantagens. Por exemplo, uma página que raramente muda, como uma página Sobre nós, não precisa ser recriada toda vez que um usuário a solicitar. Assim, um servidor pode criá-lo uma vez e depois armazená-lo em cache ou armazená-lo em algum lugar para uso futuro. Esses tipos de melhorias de velocidade podem parecer pequenas, mas em um ambiente em que o tempo até a capacidade de resposta é medido em milissegundos (ou menos), cada pedacinho conta.
Se você quiser mais informações sobre as vantagens do SSR em um ambiente Vue, confira o artigo do próprio Vue sobre SSR. Há uma variedade de opções para alcançar esses resultados, mas a mais popular, que também é recomendada pela equipe Vue, é o Nuxt.
Por que Nuxt.js
Nuxt.js é baseado em uma implementação de SSR para a popular biblioteca React chamada Next. Depois de ver as vantagens desse design, uma implementação semelhante foi projetada para o Vue chamada Nuxt. Aqueles familiarizados com a combinação React+Next identificarão várias semelhanças no design e no layout do aplicativo. No entanto, o Nuxt oferece recursos específicos do Vue para criar uma solução SSR poderosa e flexível para o Vue.
O Nuxt foi atualizado para uma versão 1.0 pronta para produção em janeiro de 2018 e faz parte de uma comunidade ativa e bem suportada. Uma das grandes coisas é que construir um projeto usando Nuxt não é tão diferente de construir qualquer outro projeto Vue. Na verdade, ele fornece vários recursos que permitem criar bases de código bem estruturadas em um período de tempo reduzido.
Outra coisa importante a ser observada é que o Nuxt não precisa ser usado para SSR . Ele é promovido como uma estrutura para criar aplicativos Vue.js universais e inclui um comando ( nuxt generate
) para criar aplicativos Vue estáticos gerados usando a mesma base de código. Portanto, se você está apreensivo em mergulhar profundamente na SSR, não entre em pânico. Você sempre pode criar um site estático enquanto ainda aproveita os recursos do Nuxt.
Para entender o potencial do Nuxt, vamos criar um projeto simples. O código-fonte final para este projeto está hospedado no GitHub, se você quiser vê-lo, ou pode ver uma versão ao vivo criada usando nuxt generate
e hospedada no Netlify.
Criando um projeto Nuxt
Para começar, vamos usar um gerador de projeto Vue chamado vue-cli
para criar rapidamente um projeto de amostra:
# install vue-cli globally npm install -g vue-cli # create a project using a nuxt template vue init nuxt-community/starter-template my-nuxt-project
Depois de passar por algumas opções, isso criará um projeto dentro da pasta my-nuxt-project
ou o que você especificou. Então só precisamos instalar as dependências e rodar o servidor:
cd my-nuxt-project npm install # Or yarn npm run dev
Aqui vamos nós. Abra seu navegador para localhost:3000
e seu projeto deve estar em execução. Não muito diferente de criar um projeto Vue Webpack. No entanto, quando olhamos para a estrutura real do aplicativo, não há muito lá, especialmente quando comparado a algo como o modelo Vue Webpack.
Olhando no package.json
também mostra que temos apenas uma dependência, o próprio Nuxt. Isso ocorre porque cada versão do Nuxt é adaptada para funcionar com versões específicas do Vue, Vue-router e Vuex e os agrupa para você.
Há também um arquivo nuxt.config.js
na raiz do projeto. Isso permite que você personalize vários recursos que o Nuxt fornece. Por padrão, ele define as tags de cabeçalho, a cor da barra de carregamento e as regras ESLint para você. Se você está ansioso para ver o que pode configurar, aqui está a documentação; estaremos cobrindo algumas opções neste artigo.
Então, o que há de tão especial sobre esses diretórios?
Layout do projeto
Se você navegar pelos diretórios criados, todos eles têm um Leiame que o acompanha, informando um breve resumo do que está nesse diretório e, muitas vezes, um link para os documentos.
Este é um benefício de usar o Nuxt: uma estrutura padrão para seu aplicativo. Qualquer bom desenvolvedor front-end estruturará um aplicativo semelhante a este, mas existem muitas ideias diferentes sobre estruturas e, ao trabalhar em equipe, inevitavelmente, algum tempo será necessário para discutir ou escolher essa estrutura. Nuxt fornece um para você.
O Nuxt procurará determinados diretórios e criará seu aplicativo para você, dependendo do que encontrar. Vamos examinar esses diretórios um por um.
Páginas
Este é o único diretório necessário . Quaisquer componentes Vue neste diretório são adicionados automaticamente ao vue-router
com base em seus nomes de arquivos e na estrutura de diretórios. Isso é extremamente conveniente. Normalmente, eu teria um diretório de páginas separado de qualquer maneira e teria que registrar manualmente cada um desses componentes em outro arquivo de roteador. Esse arquivo de roteador pode se tornar complexo para projetos maiores e pode precisar ser dividido para manter a legibilidade. Em vez disso, o Nuxt lidará com toda essa lógica para você.
Para demonstrar, podemos criar um componente Vue chamado about.vue
dentro do diretório Pages. Vamos apenas adicionar um modelo simples, como:
<template> <h1>About Page</h1> </template>
Quando você salvar, o Nuxt irá gerar novamente as rotas para você. Visto que chamamos nosso componente about.vue
, se você navegar para /about
, deverá ver esse componente. Simples.
Há um nome de arquivo que é especial. Nomear um arquivo index.vue
criará uma rota raiz para esse diretório. Quando o projeto é gerado, já existe um componente index.vue
no diretório de páginas que se correlaciona com a página inicial ou página de destino do seu site. (No exemplo de desenvolvimento, isso seria simplesmente localhost:3000
.)
E as rotas mais profundas? Subdiretórios no diretório Pages ajudam a estruturar suas rotas. Então, se quiséssemos uma página View Product, poderíamos estruturar nosso diretório Pages mais ou menos assim:
/pages --| /products ----| index.vue ----| view.vue
Agora, se navegarmos para /products/view
, veremos o componente view.vue
dentro do diretório de produtos. Se navegarmos para /products
, veremos o componente index.vue
dentro do diretório de produtos.
Você pode estar se perguntando por que não criamos apenas um componente products.vue
no diretório pages como fizemos para a página /about
. Você pode pensar que o resultado seria o mesmo, mas há uma diferença entre as duas estruturas. Vamos demonstrar isso adicionando outra nova página.
Digamos que queríamos uma página Sobre separada para cada funcionário. Por exemplo, vamos criar uma página Sobre para mim. Ele deve estar localizado em /about/ben-jones
. Inicialmente, podemos tentar estruturar o diretório Pages assim:
/pages --| about.vue --| /about ----| ben-jones.vue
Quando tentamos acessar /about/ben-jones
, obtemos o componente about.vue
, o mesmo que /about
. O que está acontecendo aqui?
Curiosamente, o que o Nuxt está fazendo aqui é gerar uma rota aninhada . Essa estrutura sugere que você deseja uma rota /about
permanente e qualquer coisa dentro dessa rota deve ser aninhada em sua própria área de visualização. No vue-router, isso seria indicado pela especificação de um componente <router-view />
dentro do componente about.vue
. No Nuxt, esse é o mesmo conceito, exceto que, em vez de <router-view />
, simplesmente usamos <nuxt />
. Então, vamos atualizar nosso componente about.vue
para permitir rotas aninhadas:
<template> <div> <h1>About Page</h1> <nuxt /> </div> </template>
Agora, quando navegamos para /about
, obtemos o componente about.vue
que tínhamos antes, com apenas um título. No entanto, quando navegamos para /about/ben-jones
, temos o título e o componente ben-jones.vue
renderizados onde o espaço reservado <nuxt/>
estava.
Não era isso que queríamos inicialmente, mas a ideia de ter uma página Sobre com uma lista de pessoas que, ao serem clicadas, preenchem uma seção da página com suas informações é um conceito interessante, então vamos deixar como está por enquanto . Se você quisesse a outra opção, tudo o que faríamos seria reestruturar nossos diretórios. Nós apenas teríamos que mover o componente about.vue
para dentro do diretório /about
e renomeá-lo como index.vue
, então a estrutura resultante seria:
/pages --| /about ----| index.vue ----| ben-jones.vue
Por fim, digamos que queremos usar parâmetros de rota para recuperar um produto específico. Por exemplo, queremos poder editar um produto navegando até /products/edit/64
onde 64 é o product_id
. Podemos fazer isso da seguinte maneira:
/pages --| /products ----| /edit ------| _product_id.vue
Observe o sublinhado no início do componente _product_id.vue
- isso significa um parâmetro de rota que é acessível no objeto $route.params
ou no objeto params
no Contexto do Nuxt (mais sobre isso posteriormente). Observe que a chave para o parâmetro será o nome do componente sem o sublinhado inicial—neste caso, product_id
—então tente mantê-los exclusivos dentro do projeto. Como resultado, em _product_id.vue
, podemos ter algo como:
<template> <h1>Editing Product {{ $route.params.product_id }}</h1> </template>
Você pode começar a imaginar layouts mais complexos, o que seria difícil de configurar usando o vue-router. Por exemplo, podemos combinar todos os itens acima em uma rota como:
/pages --| /categories ----| /_category_id ------| products.vue ------| /products --------| _product_id.vue
Não é muito difícil raciocinar sobre o que /categories/2/products/3
exibiria. Teríamos o componente products.vue
com um componente _product_id.vue
aninhado , com dois parâmetros de rota: category_id
e product_id
. Isso é muito mais simples de raciocinar do que uma configuração de roteador equivalente.
Enquanto estamos no tópico, uma coisa que costumo fazer na configuração do roteador é configurar os protetores do roteador. Como o Nuxt está construindo o roteador para nós, isso pode ser feito no próprio componente com beforeRouteEnter
. Se você deseja validar parâmetros de rota, o Nuxt fornece um método de componente chamado validate
. Portanto, se você quiser verificar se o product_id
é um número antes de tentar renderizar o componente, adicione o seguinte à tag de script de _product_id.vue
:
export default { validate ({ params }) { // Must be a number return /^\d+$/.test(params.product_id) } }
Agora, navegar para /categories/2/products/someproduct
resulta em um 404 porque someproduct
não é um número válido.
Isso é tudo para o diretório Pages. Aprender a estruturar suas rotas corretamente neste diretório é essencial, portanto, gastar um pouco de tempo inicialmente é importante para tirar o máximo proveito do Nuxt. Se você estiver procurando por uma breve visão geral, é sempre útil consultar os documentos para roteamento.
Se você está preocupado em não estar no controle do roteador, não fique. Essa configuração padrão funciona muito bem para uma ampla variedade de projetos, desde que bem estruturados. No entanto, existem alguns casos em que você pode precisar adicionar mais rotas ao roteador do que o Nuxt gera automaticamente para você ou reestruturá-las. O Nuxt fornece uma maneira de personalizar a instância do roteador na configuração, permitindo adicionar novas rotas e personalizar as rotas geradas. Você também pode editar a funcionalidade principal da instância do roteador, incluindo opções extras adicionadas pelo Nuxt. Portanto, se você encontrar um caso extremo, ainda terá a flexibilidade de encontrar a solução apropriada.
Armazenar
O Nuxt pode construir sua loja Vuex com base na estrutura do diretório da loja, semelhante ao diretório Pages. Se você não precisa de uma loja, basta remover o diretório. Existem dois modos para a loja, Clássico e Módulos.
Classic requer que você tenha um arquivo index.js
no diretório de armazenamento. Lá você precisa exportar uma função que retorna uma instância Vuex:
import Vuex from 'vuex' const createStore = () => { return new Vuex.Store({ state: ..., mutations: ..., actions: ... }) } export default createStore
Isso permite que você crie a loja da maneira que desejar, como usar o Vuex em um projeto Vue normal.
O modo Módulos também exige que você crie um arquivo index.js
no diretório de armazenamento. No entanto, este arquivo só precisa exportar o estado/mutações/ações da raiz para sua loja Vuex. O exemplo abaixo especifica um estado de raiz em branco:
export const state = () => ({})
Em seguida, cada arquivo no diretório de armazenamento será adicionado ao armazenamento em seu próprio namespace ou módulo. Por exemplo, vamos criar um lugar para armazenar o produto atual. Se criarmos um arquivo chamado product.js
no diretório da loja, uma seção com namespace da loja estará disponível em $store.product
. Aqui está um exemplo simples de como esse arquivo pode se parecer:
export const state = () => ({ _id: 0, title: 'Unknown', price: 0 }) export const actions = { load ({ commit }) { setTimeout( commit, 1000, 'update', { _id: 1, title: 'Product', price: 99.99 } ) } } export const mutations = { update (state, product) { Object.assign(state, product) } }
O setTimeout
na ação load simula algum tipo de chamada de API, que atualizará a loja com a resposta; neste caso, leva um segundo. Agora, vamos usá-lo na página de products/view
:
<template> <div> <h1>View Product {{ product._id }}</h1> <p>{{ product.title }}</p> <p>Price: {{ product.price }}</p> </div> </template> <script> import { mapState } from 'vuex' export default { created () { this.$store.dispatch('product/load') }, computed: { ...mapState(['product']) } } </script>
Algumas coisas a serem observadas: Aqui, estamos chamando nossa API falsa quando o componente é criado. Você pode ver que a ação de product/load
que estamos despachando tem namespace em Produto. Isso deixa claro exatamente com qual seção da loja estamos lidando. Então, mapeando o estado para uma propriedade local computada, podemos usá-lo facilmente em nosso modelo.
Há um problema: vemos o estado original por um segundo enquanto a API é executada. Mais tarde, usaremos uma solução fornecida pelo Nuxt para corrigir isso (conhecida como fetch
).
Apenas para enfatizar isso novamente, nunca tivemos que npm install vuex
, pois ele já está incluído no pacote Nuxt. Quando você adiciona um arquivo index.js
ao diretório de armazenamento, todos esses métodos são abertos automaticamente para você .
Esses são os dois principais diretórios explicados; o resto é muito mais simples.
Componentes
O diretório Components está lá para conter seus componentes reutilizáveis, como uma barra de navegação, galeria de imagens, paginação, tabelas de dados, etc. Visto que os componentes no diretório Pages são convertidos em rotas, você precisa de outro lugar para armazenar esses tipos de componentes. Esses componentes são acessíveis em páginas ou outros componentes importando-os:
import ComponentName from ~/components/ComponentName.vue
Bens
Isso contém ativos não compilados e tem mais a ver com como o Webpack carrega e processa arquivos, em vez de como o Nuxt funciona. Se você estiver interessado, sugiro a leitura do guia no Leiame.
Estático
Ele contém arquivos estáticos que são mapeados para o diretório raiz do seu site. Por exemplo, colocar uma imagem chamada logo.png neste diretório a tornaria disponível em /logo.png
. Isso é bom para arquivos meta como robots.txt, favicon.ico e outros arquivos que você precisa disponíveis.
Layouts
Normalmente, em um projeto Vue, você tem algum tipo de componente raiz, normalmente chamado App.vue
. Aqui é onde você pode configurar o layout do seu aplicativo (normalmente estático), que pode incluir uma barra de navegação, rodapé e uma área de conteúdo para seu roteador vue. O layout default
faz exatamente isso e é fornecido para você na pasta de layouts. Inicialmente, tudo o que tem é um div com um componente <nuxt />
(que é equivalente a <router-view />
), mas pode ser estilizado como você desejar. Por exemplo, adicionei uma barra de navegação simples ao projeto de exemplo para navegação pelas várias páginas de demonstração.

Você pode querer ter um layout diferente para uma determinada seção do seu aplicativo. Talvez você tenha algum tipo de CMS ou painel de administração que pareça diferente. Para resolver isso, crie um novo layout no diretório Layouts. Como exemplo, vamos criar um layout admin-layout.vue
que tenha apenas uma tag de cabeçalho extra e nenhuma barra de navegação:
<template> <div> <h1>Admin Layout</h1> <nuxt /> </div> </template>
Então, podemos criar uma página admin.vue
no diretório Pages e usar uma propriedade fornecida pelo Nuxt chamada layout
para especificar o nome (como uma string) do layout que queremos usar para esse componente:
<template> <h1>Admin Page</h1> </template> <script> export default { layout: 'admin-layout' } </script>
Isso é tudo o que há para isso. Os componentes da página usarão o layout default
, a menos que especificado, mas quando você navega para /admin
, ele agora usa o layout admin-layout.vue
. Obviamente, esse layout pode ser compartilhado em várias telas de administração, se desejar. A única coisa importante a lembrar é que os layouts devem conter um elemento <nuxt />
.
Há uma última coisa a notar sobre layouts. Você deve ter notado ao experimentar que, se digitar um URL inválido, uma página de erro será exibida. Esta página de erro é, na verdade, outro layout. O Nuxt tem seu próprio layout de erro (código-fonte aqui), mas se você quiser editá-lo, basta criar um layout error.vue
que será usado no lugar. A ressalva aqui é que o layout de erro não deve ter um elemento <nuxt />
. Você também terá acesso a um objeto de error
no componente com algumas informações básicas a serem exibidas. (Isso é impresso no terminal executando o Nuxt se você quiser examiná-lo.)
Middleware
Middleware são funções que podem ser executadas antes de renderizar uma página ou layout. Há uma variedade de razões pelas quais você pode querer fazê-lo. A proteção de rota é um uso popular onde você pode verificar a loja Vuex para um login válido ou validar alguns parâmetros (em vez de usar o método validate
no próprio componente). Um projeto em que trabalhei recentemente usou middleware para gerar breadcrumbs dinâmicos com base na rota e nos parâmetros.
Essas funções podem ser assíncronas; apenas tome cuidado, pois nada será mostrado ao usuário até que o middleware seja resolvido. Eles também têm acesso ao Contexto do Nuxt, que explicarei mais adiante.
Plug-ins
Este diretório permite que você registre plugins Vue antes que o aplicativo seja criado. Isso permite que o plug-in seja compartilhado em todo o seu aplicativo na instância Vue e seja acessível em qualquer componente.
A maioria dos principais plugins tem uma versão Nuxt que pode ser facilmente registrada na instância Vue seguindo seus documentos. No entanto, haverá circunstâncias em que você desenvolverá um plug-in ou precisará adaptar um plug-in existente para essa finalidade. Um exemplo que estou pegando emprestado dos documentos mostra como fazer isso para vue-notifications
. Primeiro, precisamos instalar o pacote:
npm install vue-notifications --save
Em seguida, crie um arquivo no diretório de plugins chamado vue-notifications.js
e inclua o seguinte:
import Vue from 'vue' import VueNotifications from 'vue-notifications' Vue.use(VueNotifications)
Muito semelhante a como você registraria um plugin em um ambiente Vue normal. Em seguida, edite o arquivo nuxt.config.js
na raiz do projeto e adicione a seguinte entrada ao objeto module.exports:
plugins: ['~/plugins/vue-notifications']
É isso. Agora você pode usar vue-notifications
todo o seu aplicativo. Um exemplo disso está em /plugin
no projeto de exemplo.
Então isso completa um resumo da estrutura de diretórios. Pode parecer muito para aprender, mas se você está desenvolvendo um aplicativo Vue, já está configurando o mesmo tipo de lógica. O Nuxt ajuda a abstrair a configuração e ajuda você a se concentrar na construção.
O Nuxt faz mais do que ajudar no desenvolvimento. Ele sobrecarrega seus componentes fornecendo funcionalidade extra.
Componentes sobrecarregados do Nuxt
Quando comecei a pesquisar o Nuxt, continuei lendo sobre como os componentes da página são sobrecarregados . Parecia ótimo, mas não era imediatamente óbvio o que exatamente isso significava e quais benefícios isso traz.
O que isso significa é que todos os componentes da página têm métodos extras anexados a eles que o Nuxt pode usar para fornecer funcionalidade adicional. Na verdade, já vimos um deles anteriormente quando usamos o método validate
para verificar parâmetros e redirecionar um usuário se eles forem inválidos.
Os dois principais usados em um projeto Nuxt serão os métodos asyncData
e fetch
. Ambos são muito semelhantes em conceito, são executados de forma assíncrona antes que o componente seja gerado e podem ser usados para preencher os dados de um componente e da loja. Eles também permitem que a página seja totalmente renderizada no servidor antes de enviá-la ao cliente, mesmo quando temos que esperar por alguma chamada de banco de dados ou API.
Qual é a diferença entre asyncData
e fetch
?
-
asyncData
é usado para preencher os dados do componente Page. Quando você retorna um objeto, ele é mesclado com a saída dedata
antes da renderização. -
fetch
é usado para preencher a Vuex Store. Se você retornar uma promessa, o Nuxt aguardará até que seja resolvido antes de renderizar.
Então, vamos colocar isso em bom uso. Lembra-se anteriormente na página /products/view
que tivemos um problema em que o estado inicial da loja estava sendo exibido brevemente enquanto nossa chamada de API falsa estava sendo feita? Uma maneira de corrigir isso é ter um booleano armazenado no componente ou na Loja, como loading = true
e, em seguida, exibir um componente de carregamento enquanto a chamada da API é concluída. Depois, definiríamos loading = false
e exibiríamos os dados.
Em vez disso, vamos usar fetch
para preencher a Store antes da renderização. Em uma nova página chamada /products/view-async
, vamos alterar o método created
para fetch
; isso deve funcionar, certo?
export default { fetch () { // Unfortunately the below line throws an error // because 'this.$store' is undefined... this.$store.dispatch('product/load') }, computed: {...} }
Aqui está o problema: esses métodos “sobrecarregados” são executados antes que o componente seja criado, então this
não aponta para o componente e nada nele pode ser acessado. Então, como acessamos a Loja aqui?
A API de contexto
Claro, há uma solução. Em todos os métodos do Nuxt, você recebe um argumento (normalmente o primeiro) contendo um objeto extremamente útil chamado Context. Isso é tudo o que você precisará de referência no aplicativo, o que significa que não precisamos esperar que o Vue crie essas referências no componente primeiro.
Eu recomendo verificar os documentos de contexto para ver o que está disponível. Alguns úteis são app
, onde você pode acessar todos os seus plugins, redirect
, que pode ser usado para alterar rotas, error
para exibir a página de erro e alguns autoexplicativos como route
, query
e store
.
Assim, para acessar a Store, podemos desestruturar o Context e extrair dele a Store. Também precisamos ter certeza de que retornamos uma promessa para que o Nuxt possa esperar que ela resolva antes de renderizar o componente, então precisamos fazer um pequeno ajuste em nossa ação Store também.
// Component export default { fetch ({ store }) { return store.dispatch('product/load') }, computed: {...} } // Store Action load ({ commit }) { return new Promise(resolve => { setTimeout(() => { commit('update', { _id: 1, title: 'Product', price: 99.99 }) resolve() }, 1000) }) }
Você pode usar async/await ou outros métodos dependendo do seu estilo de codificação, mas o conceito é o mesmo - estamos dizendo ao Nuxt para garantir que a chamada da API seja concluída e a Store seja atualizada com o resultado antes de tentar renderizar o componente. Se você tentar navegar para /products/view-async
, não verá o flash de conteúdo onde o produto está em seu estado inicial.
Você pode imaginar o quão útil isso pode ser em qualquer aplicativo Vue, mesmo sem SSR. O Contexto também está disponível para todos os middlewares , bem como para outros métodos Nuxt, como NuxtServerInit
, que é uma ação de armazenamento especial que é executada antes que a Loja seja inicializada (um exemplo disso está na próxima seção)
Considerações ao usar o SSR
Tenho certeza de que muitos (inclusive eu) que começam a usar uma tecnologia como o Nuxt enquanto o tratam como qualquer outro projeto Vue eventualmente atingem uma parede onde algo que sabemos que normalmente funcionaria parece impossível no Nuxt. À medida que mais dessas advertências forem documentadas, será mais fácil superar, mas a principal coisa a considerar ao começar a depurar é que o cliente e o servidor são duas entidades separadas.
Quando você acessa uma página inicialmente, uma solicitação é enviada ao Nuxt, o servidor compila o máximo possível dessa página e do restante do aplicativo e, em seguida, o servidor a envia para você. Em seguida, a responsabilidade é do cliente continuar com a navegação e carregar os fragmentos conforme necessário.
Queremos que o servidor faça o máximo possível primeiro, mas às vezes ele não tem acesso às informações de que precisa, o que resulta no trabalho sendo feito no lado do cliente. Ou pior, quando o conteúdo final apresentado pelo cliente é diferente do que o servidor esperava, o cliente é instruído a reconstruí-lo do zero. Esta é uma grande indicação de que algo está errado com a lógica do aplicativo. Felizmente, um erro será gerado no console do seu navegador (em modo de desenvolvimento) se isso começar a acontecer.
Vamos dar um exemplo de como resolver um problema comum, gerenciamento de sessão. Imagine que você tenha um aplicativo Vue onde você pode fazer login em uma conta e sua sessão é armazenada usando um token (JWT, por exemplo) que você decide manter em localStorage
. Quando você acessa o site inicialmente, deseja autenticar esse token em uma API, que retorna algumas informações básicas do usuário, se válidas, e coloca essas informações na Loja.
Depois de ler os documentos do Nuxt, você vê que existe um método útil chamado NuxtServerInit
que permite preencher de forma assíncrona a Loja uma vez no carregamento inicial. Isso soa perfeito! Então você cria seu módulo de usuário na Loja e adiciona a ação apropriada no arquivo index.js
no diretório da Loja:
export const actions = { nuxtServerInit ({ dispatch }) { // localStorage should work, right? const token = localStorage.getItem('token') if (token) return dispatch('user/load', token) } }
Ao atualizar a página, você recebe um erro, localStorage is not defined
. Pensando onde isso está acontecendo, faz sentido. Este método é executado no servidor, ele não tem ideia do que está armazenado em localStorage
no cliente; na verdade, ele nem sabe o que é “localStorage”! Então isso não é uma opção.
Então, qual é a solução? Existem alguns, na verdade. Você pode fazer com que o cliente inicialize a Loja, mas acaba perdendo os benefícios do SSR porque o cliente acaba fazendo todo o trabalho. Você pode configurar sessões no servidor e usá-las para autenticar o usuário, mas essa é outra camada a ser configurada. O que é mais semelhante ao método localStorage
é usar cookies.
O Nuxt tem acesso aos cookies porque eles são enviados com a solicitação do cliente para o servidor. Assim como acontece com outros métodos Nuxt, nuxtServerInit
tem acesso ao Context, desta vez como o segundo argumento porque o primeiro é reservado para o armazenamento. No Context, podemos acessar o objeto req
, que armazena todos os cabeçalhos e outras informações da requisição do cliente. (Isso será especialmente familiar se você tiver usado o Node.js.)
Então, depois de armazenar o token em um cookie (chamado de “token”, neste caso), vamos acessá-lo no servidor.
import Cookie from 'cookie' export const actions = { nuxtServerInit ({ dispatch }, { req }) { const cookies = Cookie.parse(req.headers.cookie || '') const token = cookies['token'] || '' if (token) return dispatch('user/load', token) } }
Uma solução simples, mas que pode não ser imediatamente óbvia. Aprender a pensar sobre onde certas ações estão acontecendo (cliente, servidor ou ambos) e o que eles têm acesso leva algum tempo, mas os benefícios valem a pena.
Desdobramento, desenvolvimento
A implantação com Nuxt é extremamente simples. Usando a mesma base de código, você pode criar um aplicativo SSR, aplicativo de página única ou página estática.
Aplicativo renderizado do lado do servidor (Aplicativo SSR)
Este é provavelmente o que você estava buscando ao usar o Nuxt. O conceito básico para implantação aqui é executar o processo de build
em qualquer plataforma que você escolher e definir algumas configurações. Vou usar o exemplo Heroku dos documentos:
Primeiro, configure scripts para Heroku em package.json
:
"scripts": { "dev": "nuxt", "build": "nuxt build", "start": "nuxt start", "heroku-postbuild": "npm run build" }
Em seguida, configure o ambiente Heroku usando o heroku-cli
(instruções de configuração aqui:
# set Heroku variables heroku config:set NPM_CONFIG_PRODUCTION=false heroku config:set HOST=0.0.0.0 heroku config:set NODE_ENV=production # deploy git push heroku master
É isso. Agora seu aplicativo SSR Vue está pronto para o mundo ver. Outras plataformas têm configurações diferentes, mas o processo é semelhante. Os métodos oficiais de implantação atualmente listados são:
- Agora
- Dokku (Oceano Digital)
- Nginx
Aplicativo de página única (SPA)
Se você quiser aproveitar alguns dos recursos extras fornecidos pelo Nuxt, mas evitar que o servidor tente renderizar páginas, poderá implantar como um SPA.
Primeiro, é melhor testar seu aplicativo sem o SSR, pois, por padrão, o npm run dev
é executado com o SSR ativado. Para mudar isso, edite o arquivo nuxt.config.js
e adicione a seguinte opção:
mode: 'spa',
Agora, quando você executar npm run dev
, o SSR será desativado e o aplicativo será executado como um SPA para você testar. Essa configuração também garante que nenhuma compilação futura inclua SSR.
Se tudo estiver bem, a implantação é exatamente a mesma de um aplicativo SSR. Apenas lembre-se de que você precisa definir o mode: 'spa'
primeiro para que o processo de compilação saiba que você deseja um SPA.
Páginas estáticas
Se você não deseja lidar com um servidor e, em vez disso, deseja gerar páginas para uso com serviços de hospedagem estática, como Surge ou Netlify, essa é a opção a escolher. Apenas tenha em mente que, sem um servidor, você não poderá acessar o req
e res
no Context, portanto, se seu código depende disso, certifique-se de acomodá-lo. Por exemplo, ao gerar o projeto de exemplo, a função nuxtServerInit
gera um erro porque está tentando buscar um token dos cookies nos cabeçalhos da solicitação. Neste projeto, não importa, pois esses dados não estão sendo usados em nenhum lugar, mas em uma aplicação real, precisaria haver uma maneira alternativa de acessar esses dados.
Uma vez que é classificado, a implantação é fácil. Uma coisa que você provavelmente precisará mudar primeiro é adicionar uma opção para que o comando nuxt generate
também crie um arquivo de fallback. Esse arquivo solicitará que o serviço de hospedagem permita que o Nuxt lide com o roteamento em vez do serviço de hospedagem, gerando um erro 404. Para fazer isso, adicione a seguinte linha a nuxt.config.js
:
generate: { fallback: true },
Aqui está um exemplo usando Netlify, que não está atualmente nos documentos do Nuxt. Apenas tenha em mente que, se esta for a primeira vez que você usa netlify-cli
, você será solicitado a autenticar:
# install netlify-cli globally npm install netlify-cli -g # generate the application (outputs to dist/ folder) npm run generate # deploy netlify deploy dist
É simples assim! Como mencionado no início do artigo, há uma versão deste projeto aqui. Também há documentação oficial de implantação para os seguintes serviços abaixo:
- Surto
- Páginas do GitHub
Saber mais
O Nuxt está atualizando rapidamente, e esta é apenas uma pequena seleção dos recursos que ele oferece. Espero que este artigo encoraje você a experimentá-lo e ver se ele pode ajudar a melhorar os recursos de seus aplicativos Vue, permitindo que você desenvolva mais rapidamente e aproveite seus recursos poderosos.
Se você está procurando mais informações, não procure mais, os links oficiais do Nuxt:
- Documentação
- Parque infantil
- GitHubGenericName
- Perguntas frequentes
Looking to up your JavaScript game? Try reading The Comprehensive Guide to JavaScript Design Patterns by fellow Toptaler Marko Mišura.