Создание серверных визуализированных приложений Vue.js с использованием Nuxt.js

Опубликовано: 2022-03-11

Фреймворки/библиотеки JavaScript, такие как Vue, могут предложить фантастический пользовательский интерфейс при просмотре вашего сайта. Большинство из них предлагают способ динамического изменения содержимого страницы без необходимости каждый раз отправлять запрос на сервер.

Однако с этим подходом есть проблема. При первоначальной загрузке вашего веб-сайта ваш браузер не получает полную страницу для отображения. Вместо этого ему отправляется набор фрагментов для создания страницы (HTML, CSS, другие файлы) и инструкции о том, как собрать их все вместе (фреймворк/библиотека JavaScript). Сбор всей этой информации занимает ощутимое количество времени. прежде чем ваш браузер на самом деле будет что-то отображать. Это похоже на то, как если бы вам прислали кучу книг вместе с книжным шкафом. Сначала вам нужно построить книжный шкаф, а затем заполнить его книгами.

Умное решение: иметь на сервере версию фреймворка/библиотеки, которая может создать готовую к отображению страницу. Затем отправьте эту полную страницу в браузер вместе с возможностью внесения дальнейших изменений и по-прежнему иметь динамическое содержимое страницы (фреймворк/библиотека), точно так же, как при отправке готового книжного шкафа вместе с некоторыми книгами. Конечно, вам все равно придется ставить книги в книжный шкаф, но у вас есть кое-что, что можно использовать немедленно.

Визуальное сравнение рендеринга на стороне клиента и на стороне сервера

Помимо глупой аналогии, есть еще куча других преимуществ. Например, страницу, которая редко меняется, например страницу «О нас», не нужно создавать заново каждый раз, когда пользователь запрашивает ее. Таким образом, сервер может создать его один раз, а затем кэшировать или сохранить где-нибудь для будущего использования. Такого рода улучшения скорости могут показаться крошечными, но в среде, где время до отклика измеряется миллисекундами (или меньше), важен каждый бит.

Если вам нужна дополнительная информация о преимуществах SSR в среде Vue, вам следует ознакомиться с собственной статьей Vue о SSR. Существует множество вариантов для достижения этих результатов, но самым популярным, который также рекомендуется командой Vue, является Nuxt.

Почему Nuxt.js

Nuxt.js основан на реализации SSR для популярной библиотеки React под названием Next. Увидев преимущества этого дизайна, аналогичная реализация была разработана для Vue под названием Nuxt. Те, кто знаком с комбинацией React+Next, заметят много общего в дизайне и компоновке приложения. Однако Nuxt предлагает специфичные для Vue функции для создания мощного, но гибкого решения SSR для Vue.

Nuxt был обновлен до готовой к работе версии 1.0 в январе 2018 года и является частью активного и хорошо поддерживаемого сообщества. Одна из замечательных вещей заключается в том, что создание проекта с использованием Nuxt ничем не отличается от создания любого другого проекта Vue. Фактически, он предоставляет набор функций, которые позволяют создавать хорошо структурированные кодовые базы за меньшее время.

Еще одна важная вещь, которую следует отметить, это то, что Nuxt не обязательно использовать для SSR . Он рекламируется как платформа для создания универсальных приложений Vue.js и включает в себя команду ( nuxt generate ) для создания статически сгенерированных приложений Vue с использованием той же кодовой базы. Поэтому, если вы опасаетесь углубляться в SSR, не паникуйте. Вместо этого вы всегда можете создать статический сайт, при этом используя возможности Nuxt.

Чтобы понять потенциал Nuxt, давайте создадим простой проект. Окончательный исходный код для этого проекта размещен на GitHub, если вы хотите его увидеть, или вы можете просмотреть живую версию, созданную с помощью nuxt generate и размещенную на Netlify.

Создание проекта Nuxt

Для начала давайте воспользуемся генератором проектов Vue под названием vue-cli , чтобы быстро создать пример проекта:

 # 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

После прохождения пары опций будет создан проект внутри папки my-nuxt-project или любой другой, которую вы указали. Затем нам просто нужно установить зависимости и запустить сервер:

 cd my-nuxt-project npm install # Or yarn npm run dev

Ну вот. Откройте в браузере localhost:3000 , и ваш проект должен быть запущен. Не сильно отличается от создания проекта Vue Webpack. Однако, когда мы смотрим на реальную структуру приложения, там не так много, особенно по сравнению с чем-то вроде шаблона Vue Webpack.

Схема каталогов проекта и их связь с конфигурационным файлом Nuxt

Просмотр package.json также показывает, что у нас есть только одна зависимость, сам Nuxt. Это связано с тем, что каждая версия Nuxt предназначена для работы с определенными версиями Vue, Vue-router и Vuex и объединяет их для вас.

Также в корне проекта есть файл nuxt.config.js . Это позволяет вам настраивать множество функций, которые предоставляет Nuxt. По умолчанию он устанавливает для вас теги заголовков, цвет полосы загрузки и правила ESLint. Если вам не терпится узнать, что можно настроить, вот документация; мы рассмотрим некоторые варианты в этой статье.

Так что же такого особенного в этих каталогах?

Макет проекта

Если вы просматриваете созданные каталоги, все они имеют сопровождающий файл Readme с кратким описанием того, что находится в этом каталоге, и часто со ссылкой на документы.

Это одно из преимуществ использования Nuxt: структура по умолчанию для вашего приложения. Любой хороший фронтенд-разработчик структурирует приложение, подобное этому, но есть много разных идей о структурах, и при работе в команде неизбежно уходит некоторое время на обсуждение или выбор этой структуры. Nuxt предоставляет один для вас.

Nuxt будет искать определенные каталоги и создавать для вас приложение в зависимости от того, что найдет. Давайте рассмотрим эти каталоги один за другим.

Страницы

Это единственный необходимый каталог. Любые компоненты Vue в этом каталоге автоматически добавляются в vue-router на основе их имен файлов и структуры каталогов. Это очень удобно. Обычно у меня в любом случае был бы отдельный каталог Pages, и мне приходилось вручную регистрировать каждый из этих компонентов в другом файле маршрутизатора. Этот файл маршрутизатора может стать сложным для более крупных проектов и может потребовать разделения для обеспечения удобочитаемости. Вместо этого Nuxt будет обрабатывать всю эту логику за вас.

Для демонстрации мы можем создать компонент Vue с именем about.vue внутри каталога Pages. Давайте просто добавим простой шаблон, такой как:

 <template> <h1>About Page</h1> </template>

Когда вы сохраните, Nuxt повторно сгенерирует маршруты для вас. Поскольку мы назвали наш компонент about.vue , если вы перейдете к /about , вы должны увидеть этот компонент. Простой.

Есть одно имя файла, которое является особенным. Имя файла index.vue создаст корневой маршрут для этого каталога. Когда проект создан, в каталоге страниц уже есть компонент index.vue , который соответствует домашней или целевой странице вашего сайта. (В примере разработки это будет просто localhost:3000 .)

Nuxt сканирует файлы Vue в каталоге pages и выводит соответствующие страницы.

А как насчет более глубоких маршрутов? Подкаталоги в каталоге Pages помогают структурировать ваши маршруты. Итак, если нам нужна страница «Просмотр продукта», мы могли бы структурировать наш каталог Pages примерно так:

 /pages --| /products ----| index.vue ----| view.vue

Теперь, если мы перейдем к /products/view , мы увидим компонент view.vue внутри каталога продуктов. Если вместо этого мы перейдем к /products , мы увидим компонент index.vue внутри каталога продуктов.

Вы можете спросить, почему мы просто не создали компонент products.vue в каталоге pages вместо того, чтобы сделать это для страницы /about . Вы можете подумать, что результат будет таким же, но между двумя структурами есть разница. Давайте продемонстрируем это, добавив еще одну новую страницу.

Скажем, нам нужна отдельная страница «О нас» для каждого сотрудника. Например, давайте создадим для меня страницу «О нас». Он должен находиться в /about/ben-jones . Первоначально мы можем попробовать структурировать каталог Pages следующим образом:

 /pages --| about.vue --| /about ----| ben-jones.vue

Когда мы пытаемся получить доступ к /about/ben-jones , вместо этого мы получаем компонент about.vue , такой же, как /about . Что тут происходит?

Интересно, что здесь Nuxt генерирует вложенный маршрут . Эта структура предполагает, что вам нужен постоянный маршрут /about , и все, что находится внутри этого маршрута, должно быть вложено в свою собственную область просмотра. В vue-router это будет обозначаться указанием компонента <router-view /> внутри компонента about.vue . В Nuxt используется та же концепция, за исключением того, что вместо <router-view /> мы просто используем <nuxt /> . Итак, давайте обновим наш компонент about.vue , чтобы разрешить вложенные маршруты:

 <template> <div> <h1>About Page</h1> <nuxt /> </div> </template>

Теперь, когда мы переходим к /about , мы получаем компонент about.vue , который у нас был раньше, только с заголовком. Однако, когда мы переходим к /about/ben-jones , у нас вместо этого отображается заголовок и компонент ben-jones.vue там, где был заполнитель <nuxt/> .

Это было не то, что мы изначально хотели, но идея иметь страницу «О нас» со списком людей, которые при нажатии на них заполняют раздел страницы своей информацией, — интересная концепция, поэтому давайте пока оставим ее как есть. . Если вам нужен другой вариант, то все, что мы сделаем, это реструктурируем наши каталоги. Нам просто нужно переместить компонент about.vue в каталог /about и переименовать его index.vue , чтобы в результате получилась следующая структура:

 /pages --| /about ----| index.vue ----| ben-jones.vue

Наконец, предположим, что мы хотим использовать параметры маршрута для получения определенного продукта. Например, мы хотим иметь возможность редактировать продукт, перейдя к /products/edit/64 , где 64 — это product_id . Мы можем сделать это следующим образом:

 /pages --| /products ----| /edit ------| _product_id.vue

Обратите внимание на подчеркивание в начале компонента _product_id.vue — это означает параметр маршрута, который затем доступен в объекте $route.params или в объекте params в контексте Nuxt (подробнее об этом позже). Обратите внимание, что ключом для параметра будет имя компонента без начального подчеркивания — в данном случае product_id — поэтому постарайтесь, чтобы они были уникальными в рамках проекта. В результате в _product_id.vue у нас может быть что-то вроде:

 <template> <h1>Editing Product {{ $route.params.product_id }}</h1> </template>

Вы можете начать представлять себе более сложные макеты, которые было бы сложно настроить с помощью vue-router. Например, мы можем объединить все вышеперечисленное в такой маршрут, как:

 /pages --| /categories ----| /_category_id ------| products.vue ------| /products --------| _product_id.vue

Нетрудно понять, что будет отображаться в /categories/2/products/3 . У нас будет компонент products.vue с вложенным компонентом _product_id.vue с двумя параметрами маршрута: category_id и product_id . Об этом гораздо проще рассуждать, чем об эквивалентной конфигурации маршрутизатора.

Пока мы обсуждаем эту тему, одна вещь, которую я обычно делаю в конфигурации маршрутизатора, — это настройка защиты маршрутизатора. Поскольку Nuxt создает для нас маршрутизатор, это можно сделать на самом компоненте с помощью beforeRouteEnter . Если вы хотите проверить параметры маршрута, Nuxt предоставляет метод компонента под названием validate . Поэтому, если вы хотите проверить, является ли product_id числом, прежде чем пытаться отобразить компонент, вы должны добавить следующее в тег скрипта _product_id.vue :

 export default { validate ({ params }) { // Must be a number return /^\d+$/.test(params.product_id) } }

Теперь переход к /categories/2/products/someproduct приводит к ошибке 404, потому что someproduct не является допустимым числом.

Вот и все для каталога Pages. Очень важно научиться правильно структурировать свои маршруты в этом каталоге, поэтому сначала нужно потратить немного времени, чтобы получить максимальную отдачу от Nuxt. Если вам нужен краткий обзор, всегда полезно обратиться к документации по маршрутизации.

Если вы беспокоитесь о том, что не сможете контролировать маршрутизатор, не беспокойтесь. Эта настройка по умолчанию отлично подходит для самых разных проектов, если они хорошо структурированы. Однако в некоторых случаях вам может потребоваться добавить к маршрутизатору больше маршрутов, чем Nuxt автоматически генерирует для вас, или реструктурировать их. Nuxt предоставляет возможность настроить экземпляр маршрутизатора в конфигурации, позволяя добавлять новые маршруты и настраивать сгенерированные маршруты. Вы также можете редактировать основные функции экземпляра маршрутизатора, включая дополнительные параметры, добавленные Nuxt. Поэтому, если вы столкнетесь с крайним случаем, у вас все еще есть возможность найти подходящее решение.

Магазин

Nuxt может построить ваш магазин Vuex на основе структуры каталога хранилища, аналогичного каталогу Pages. Если вам не нужен магазин, просто удалите каталог. В магазине есть два режима: Классический и Модули.

Classic требует наличия файла index.js в каталоге магазина. Там вам нужно экспортировать функцию, которая возвращает экземпляр Vuex:

 import Vuex from 'vuex' const createStore = () => { return new Vuex.Store({ state: ..., mutations: ..., actions: ... }) } export default createStore

Это позволяет вам создавать хранилище так, как вы хотите, так же, как использование Vuex в обычном проекте Vue.

Режим модулей также требует , чтобы вы создали файл index.js в каталоге хранилища. Однако этому файлу нужно только экспортировать корневое состояние/мутации/действия для вашего хранилища Vuex. В приведенном ниже примере указано пустое корневое состояние:

 export const state = () => ({})

Затем каждый файл в каталоге хранилища будет добавлен в хранилище в своем собственном пространстве имен или модуле. Например, давайте создадим место для хранения текущего продукта. Если мы создадим файл с именем product.js в каталоге магазина, то раздел магазина с пространством имен будет доступен в $store.product . Вот простой пример того, как может выглядеть этот файл:

 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) } }

setTimeout в действии загрузки имитирует некий вызов API, который обновит хранилище ответом; в этом случае это занимает одну секунду. Теперь давайте используем его на странице 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>

Несколько замечаний: здесь мы вызываем наш поддельный API при создании компонента. Вы можете видеть, что действие product/load , которое мы отправляем, находится в пространстве имен Product. Это дает понять, с каким именно разделом магазина мы имеем дело. Затем, сопоставив состояние с локальным вычисляемым свойством, мы можем легко использовать его в нашем шаблоне.

Есть проблема: мы видим исходное состояние на секунду, пока работает API. Позже мы будем использовать решение, предоставленное Nuxt, чтобы исправить это (известное как fetch ).

Просто чтобы подчеркнуть это еще раз, нам никогда не приходилось npm install vuex , так как он уже включен в пакет Nuxt. Когда вы добавляете файл index.js в каталог хранилища, все эти методы автоматически открываются для вас.

Это основные два каталога; остальные намного проще.

Компоненты

Каталог Components предназначен для хранения ваших многократно используемых компонентов, таких как панель навигации, галерея изображений, разбиение на страницы, таблицы данных и т. д. Поскольку компоненты в каталоге Pages преобразуются в маршруты, вам нужно где-то еще хранить эти типы компонентов. Эти компоненты доступны на страницах или в других компонентах путем их импорта:

 import ComponentName from ~/components/ComponentName.vue

Ресурсы

Он содержит нескомпилированные активы и больше связан с тем, как Webpack загружает и обрабатывает файлы, а не с тем, как работает Nuxt. Если вам интересно, я предлагаю прочитать руководство в файле Readme.

Статический

Он содержит статические файлы, которые сопоставляются с корневым каталогом вашего сайта. Например, размещение изображения с именем logo.png в этом каталоге сделает его доступным по адресу /logo.png . Это хорошо для метафайлов, таких как robots.txt, favicon.ico и других файлов, которые вам нужны.

Макеты

Обычно в проекте Vue у вас есть какой-то корневой компонент, который обычно называется App.vue . Здесь вы можете настроить свой (обычно статический) макет приложения, который может включать панель навигации, нижний колонтитул, а затем область содержимого для вашего vue-router. Макет default делает именно это и предоставляется вам в папке макетов. Изначально все, что у него есть, это div с компонентом <nuxt /> (что эквивалентно <router-view /> ), но его можно стилизовать по своему усмотрению. Например, я добавил в пример проекта простую панель навигации для навигации по различным демонстрационным страницам.

Макет можно применить к нескольким страницам.

Вы можете захотеть иметь другой макет для определенного раздела вашего приложения. Возможно, у вас есть какая-то CMS или панель администратора, которая выглядит по-другому. Чтобы решить эту проблему, создайте новый макет в каталоге Layouts. В качестве примера давайте создадим макет admin-layout.vue , который имеет только дополнительный тег заголовка и не имеет панели навигации:

 <template> <div> <h1>Admin Layout</h1> <nuxt /> </div> </template>

Затем мы можем создать страницу admin.vue в каталоге Pages и использовать свойство layout , предоставленное Nuxt, чтобы указать имя (в виде строки) макета, который мы хотим использовать для этого компонента:

 <template> <h1>Admin Page</h1> </template> <script> export default { layout: 'admin-layout' } </script>

Вот и все. Компоненты страницы будут использовать макет по default , если он не указан, но при переходе к /admin теперь используется макет admin-layout.vue . Конечно, при желании этот макет можно использовать на нескольких экранах администратора. Важно помнить одну важную вещь: макеты должны содержать элемент <nuxt /> .

И последнее, что следует отметить о макетах. Возможно, вы заметили во время экспериментов, что если вы введете неверный URL-адрес, вам будет показана страница с ошибкой. Эта страница с ошибкой, по сути, является другим макетом. Nuxt имеет свой собственный макет ошибок (исходный код здесь), но если вы хотите его отредактировать, просто создайте макет error.vue , и он будет использоваться вместо него. Предупреждение здесь заключается в том, что в макете ошибки не должно быть элемента <nuxt /> . У вас также будет доступ к объекту error компонента с некоторой базовой информацией для отображения. (Это распечатывается в терминале, на котором работает Nuxt, если вы хотите его изучить.)

ПО промежуточного слоя

Промежуточное программное обеспечение — это функции, которые можно выполнить перед рендерингом страницы или макета. Существует множество причин, по которым вы можете захотеть это сделать. Защита маршрута — это популярное использование, когда вы можете проверить хранилище Vuex на наличие действительного входа или проверить некоторые параметры (вместо использования метода validate на самом компоненте). Один проект, над которым я недавно работал, использовал промежуточное программное обеспечение для создания динамических хлебных крошек на основе маршрута и параметров.

Эти функции могут быть асинхронными; просто будьте осторожны, так как пользователю ничего не будет показано, пока промежуточное ПО не будет разрешено. У них также есть доступ к контексту Nuxt, о котором я расскажу позже.

Плагины

Этот каталог позволяет вам регистрировать плагины Vue до создания приложения. Это позволяет совместно использовать плагин в вашем приложении на экземпляре Vue и быть доступным в любом компоненте.

У большинства основных плагинов есть версия Nuxt, которую можно легко зарегистрировать в экземпляре Vue, следуя их документации. Однако могут возникнуть обстоятельства, когда вы будете разрабатывать плагин или вам потребуется адаптировать существующий плагин для этой цели. Пример, который я заимствую из документации, показывает, как это сделать для vue-notifications . Во-первых, нам нужно установить пакет:

 npm install vue-notifications --save

Затем создайте файл в каталоге плагинов с именем vue-notifications.js и включите в него следующее:

 import Vue from 'vue' import VueNotifications from 'vue-notifications' Vue.use(VueNotifications)

Очень похоже на то, как вы зарегистрировали бы плагин в обычной среде Vue. Затем отредактируйте файл nuxt.config.js в корне вашего проекта и добавьте следующую запись в объект module.exports:

 plugins: ['~/plugins/vue-notifications']

Вот и все. Теперь вы можете использовать vue-notifications во всем приложении. Пример этого находится в /plugin в примере проекта.

Итак, это завершает изложение структуры каталогов. Может показаться, что нужно многому научиться, но если вы разрабатываете приложение Vue, вы уже настраиваете такую ​​же логику. Nuxt помогает абстрагироваться от настройки и помогает сосредоточиться на создании.

Однако Nuxt не только помогает в разработке. Он перегружает ваши компоненты, предоставляя дополнительную функциональность.

Компоненты Nuxt с наддувом

Когда я впервые начал исследовать Nuxt, я все время читал о том, как компоненты страницы перегружаются . Это звучало здорово, но не сразу было понятно, что именно это означает и какие преимущества это приносит.

Это означает, что ко всем компонентам страницы прикреплены дополнительные методы, которые Nuxt может использовать для предоставления дополнительных функций. Фактически, мы уже видели один из них ранее, когда использовали метод validate для проверки параметров и перенаправления пользователя, если они недействительны.

Двумя основными из них, используемыми в проекте Nuxt, будут методы asyncData и fetch . Оба очень похожи по концепции, они запускаются асинхронно до создания компонента, и их можно использовать для заполнения данными компонента и хранилища. Они также позволяют полностью отобразить страницу на сервере перед ее отправкой клиенту, даже если нам приходится ждать вызова какой-либо базы данных или API.

В чем разница между asyncData и fetch ?

  • asyncData используется для заполнения данных компонента Page. Когда вы возвращаете объект, он затем объединяется с выходными data перед рендерингом.
  • fetch используется для заполнения Vuex Store. Если вы возвращаете обещание, Nuxt будет ждать, пока оно не будет разрешено, прежде чем выполнять рендеринг.

Так что давайте использовать их с пользой. Помните, ранее на странице /products/view у нас была проблема, когда начальное состояние магазина ненадолго отображалось во время выполнения нашего фальшивого вызова API? Один из способов исправить это — хранить логическое значение в компоненте или в Store, например loading = true , а затем отображать загружаемый компонент по завершении вызова API. После этого мы установили бы loading = false и отобразили бы данные.

Вместо этого давайте воспользуемся fetch для заполнения Store перед рендерингом. На новой странице с именем /products/view-async давайте изменим created метод на fetch ; это должно работать, верно?

 export default { fetch () { // Unfortunately the below line throws an error // because 'this.$store' is undefined... this.$store.dispatch('product/load') }, computed: {...} }

Вот в чем загвоздка: эти «перегруженные» методы запускаются до создания компонента, поэтому this не указывает на компонент, и доступ к нему невозможен. Итак, как нам получить доступ к Магазину здесь?

Контекстный API

Конечно, есть решение. Во всех методах Nuxt вам предоставляется аргумент (обычно первый), содержащий чрезвычайно полезный объект, который называется Context. Это все, на что вам понадобятся ссылки в приложении, а это означает, что нам не нужно ждать, пока Vue сначала создаст эти ссылки в компоненте.

Я настоятельно рекомендую ознакомиться с документацией Context, чтобы узнать, что доступно. Некоторыми удобными являются app , где вы можете получить доступ ко всем своим плагинам, redirect , который можно использовать для изменения маршрутов, error для отображения страницы с ошибкой и некоторые не требующие пояснений, такие как route , query и store .

Итак, чтобы получить доступ к Store, мы можем деструктурировать Context и извлечь из него Store. Нам также нужно убедиться, что мы возвращаем обещание, чтобы Nuxt мог дождаться его разрешения перед визуализацией компонента, поэтому нам также нужно внести небольшую корректировку в наше действие Store.

 // 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) }) }

Вы можете использовать async/await или другие методы в зависимости от вашего стиля кодирования, но концепция та же — мы сообщаем Nuxt, чтобы убедиться, что вызов API завершен, а Store обновлен с результатом, прежде чем пытаться отобразить компонент. Если вы попытаетесь перейти к /products/view-async , вы не увидите вспышку содержимого, где продукт находится в исходном состоянии.

Вы можете себе представить, насколько полезным это может быть в любом приложении Vue даже без SSR. Контекст также доступен для всех промежуточных программ , а также для других методов Nuxt, таких как NuxtServerInit , который представляет собой специальное действие хранилища, которое запускается до инициализации хранилища (пример этого приведен в следующем разделе).

Соображения при использовании SSR

Я уверен, что многие (включая меня), которые начинают использовать такую ​​технологию, как Nuxt, рассматривая ее как любой другой проект Vue, в конечном итоге упираются в стену, где то, что, как мы знаем, обычно работает, кажется невозможным в Nuxt. Чем больше таких предостережений будет задокументировано, тем легче их будет преодолеть, но главное, что следует учитывать при запуске отладки, это то, что клиент и сервер — это два отдельных объекта.

Когда вы впервые обращаетесь к странице, в Nuxt отправляется запрос, сервер строит как можно больше этой страницы и остальной части приложения, а затем сервер отправляет его вам. Затем на клиенте лежит ответственность за продолжение навигации и загрузку фрагментов по мере необходимости.

Мы хотим, чтобы сервер сначала делал как можно больше, но иногда он не имеет доступа к необходимой ему информации, что приводит к тому, что вместо этого работа выполняется на стороне клиента. Или, что еще хуже, когда окончательный контент, представленный клиентом, отличается от ожидаемого сервером, клиенту предлагается перестроить его с нуля. Это явный признак того, что что-то не так с логикой приложения. К счастью, в консоли вашего браузера (в режиме разработки) будет сгенерирована ошибка, если это начнет происходить.

Давайте возьмем пример того, как решить распространенную проблему, управление сеансом. Представьте, что у вас есть приложение Vue, в котором вы можете войти в учетную запись, и ваша сессия хранится с использованием токена (например, JWT), который вы решили сохранить в localStorage . Когда вы впервые получаете доступ к сайту, вы хотите аутентифицировать этот токен с помощью API, который возвращает некоторую базовую информацию о пользователе, если она действительна, и помещает эту информацию в Магазин.

Прочитав документацию Nuxt, вы увидите, что есть удобный метод под названием NuxtServerInit , который позволяет вам асинхронно заполнять Store один раз при начальной загрузке. Это звучит идеально! Итак, вы создаете свой пользовательский модуль в Store и добавляете соответствующее действие в файл index.js в каталоге Store:

 export const actions = { nuxtServerInit ({ dispatch }) { // localStorage should work, right? const token = localStorage.getItem('token') if (token) return dispatch('user/load', token) } }

При обновлении страницы выдает ошибку, localStorage is not defined . Думая о том, где это происходит, это имеет смысл. Этот метод запускается на сервере, он понятия не имеет, что хранится в localStorage на клиенте; на самом деле, он даже не знает, что такое «localStorage»! Так что это не вариант.

Сервер пытается выполнить localStorage.getItem('token'), но выдает ошибку, а затем заголовок ниже, объясняющий проблему.

Итак, каково решение? На самом деле их несколько. Вместо этого вы можете заставить клиента инициализировать Store, но в конечном итоге вы потеряете преимущества SSR, потому что клиент в конечном итоге сделает всю работу. Вы можете настроить сеансы на сервере, а затем использовать их для аутентификации пользователя, но это еще один уровень настройки. Что больше всего похоже на метод localStorage , так это использование файлов cookie.

Nuxt имеет доступ к файлам cookie, поскольку они отправляются вместе с запросом от клиента к серверу. Как и другие методы Nuxt, nuxtServerInit имеет доступ к контексту, на этот раз в качестве второго аргумента, поскольку первый зарезервирован для хранилища. В контексте мы можем получить доступ к объекту req , в котором хранятся все заголовки и другая информация из клиентского запроса. (Это будет особенно знакомо, если вы использовали Node.js.)

Поэтому после сохранения токена в файле cookie (в данном случае называемом «токен») давайте получим к нему доступ на сервере.

 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) } }

Простое решение, но оно может быть не сразу очевидным. Научиться думать о том, где происходят определенные действия (клиент, сервер или и то, и другое) и к чему у них есть доступ, занимает некоторое время, но преимущества того стоят.

Развертывание

Развертывание с Nuxt чрезвычайно просто. Используя ту же кодовую базу, вы можете создать приложение SSR, одностраничное приложение или статическую страницу.

Приложение с визуализацией на стороне сервера (приложение SSR)

Вероятно, это то, к чему вы стремились при использовании Nuxt. Основная концепция развертывания здесь заключается в запуске процесса build на любой платформе, которую вы выберете, и установке нескольких конфигураций. Я буду использовать пример Heroku из документации:

Сначала настройте скрипты для Heroku в package.json :

 "scripts": { "dev": "nuxt", "build": "nuxt build", "start": "nuxt start", "heroku-postbuild": "npm run build" }

Затем настройте среду Heroku с помощью heroku-cli (инструкции по установке здесь:

 # 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

Вот и все. Теперь ваше приложение SSR Vue готово для всего мира. Другие платформы имеют другие настройки, но процесс аналогичен. В настоящее время перечислены официальные методы развертывания:

  • Сейчас
  • Докку (Цифровой океан)
  • Nginx

Одностраничное приложение (SPA)

Если вы хотите воспользоваться некоторыми дополнительными функциями, предоставляемыми Nuxt, но при этом избежать попыток сервера отобразить страницы, вы можете вместо этого развернуться как SPA.

Во-первых, лучше протестировать ваше приложение без SSR, так как по умолчанию npm run dev запускается с включенным SSR. Чтобы изменить это, отредактируйте файл nuxt.config.js и добавьте следующую опцию:

 mode: 'spa',

Теперь, когда вы запускаете npm run dev , SSR будет отключен, и приложение будет работать как SPA для тестирования. Этот параметр также гарантирует, что ни одна из будущих сборок не будет включать SSR.

Если все выглядит нормально, то развертывание точно такое же, как и для приложения SSR. Просто помните, что вам нужно сначала установить mode: 'spa' чтобы процесс сборки знал, что вам нужен SPA.

Статические страницы

Если вы вообще не хотите иметь дело с сервером и вместо этого хотите создавать страницы для использования со статическими службами хостинга, такими как Surge или Netlify, то это вариант для выбора. Просто имейте в виду, что без сервера вы не сможете получить доступ к req и res в контексте, поэтому, если ваш код зависит от этого, обязательно приспособьтесь к этому. Например, при создании примерного проекта функция nuxtServerInit выдает ошибку, поскольку она пытается получить токен из файлов cookie в заголовках запроса. В этом проекте это не имеет значения, так как эти данные нигде не используются, но в реальном приложении должен быть альтернативный способ доступа к этим данным.

Как только это будет отсортировано, развертывание будет простым. Одна вещь, которую вам, вероятно, нужно будет изменить в первую очередь, — это добавить параметр, чтобы команда nuxt generate также создавала резервный файл. Этот файл предложит службе хостинга разрешить Nuxt обрабатывать маршрутизацию, а не службу хостинга, выдавая ошибку 404. Для этого добавьте следующую строку в nuxt.config.js :

 generate: { fallback: true },

Вот пример использования Netlify, которого в настоящее время нет в документации Nuxt. Просто имейте в виду, что если вы впервые используете netlify-cli , вам будет предложено пройти аутентификацию:

 # install netlify-cli globally npm install netlify-cli -g # generate the application (outputs to dist/ folder) npm run generate # deploy netlify deploy dist

Это так просто! Как упоминалось в начале статьи, здесь есть версия этого проекта. Ниже также представлена ​​официальная документация по развертыванию для следующих сервисов:

  • Всплеск
  • Страницы GitHub

Узнать больше

Nuxt быстро обновляется, и это лишь малая часть предлагаемых им функций. Я надеюсь, что эта статья побудит вас попробовать его и посмотреть, поможет ли он улучшить возможности ваших приложений Vue, что позволит вам быстрее разрабатывать и использовать его мощные функции.

Если вам нужна дополнительная информация, не ищите дальше официальных ссылок Nuxt:

  • Документация
  • Игровая площадка
  • Гитхаб
  • Вопросы-Ответы

Looking to up your JavaScript game? Try reading The Comprehensive Guide to JavaScript Design Patterns by fellow Toptaler Marko Mišura.