Готовый к продакшену Vue SSR: 5 простых шагов

Я работаю в компании Namecheap на позиции Senior Software Engineer. В нашей компании мы используем Vue.js с серверным рендерингом для некоторых наших страниц. Настроить SSR может быть не так легко, поэтому я попытался описать этот процесс простыми шагами. Также, читая официальную документацию, можно подумать, что было бы полезно увидеть как приложение должно выглядеть в итоге. Поэтому я создал репозиторий с примером.

В этой статье мы рассмотрим, как настроить готовый к продакшену SSR для Vue-приложения, используя:

  • Webpack 4;
  • Babel 7;
  • Node.js Express сервер;
  • webpack-dev-middleware и webpack-hot-middleware для удобной разработки;
  • Vuex для управления состоянием приложения;
  • плагин vue-meta для управления метаданными.

Отмечу, что мы не будем останавливаться на деталях использования этих технологий, а сосредоточимся только на SSR. Надеюсь, это будет полезно. Поехали!

Шаг 1. Настройка webpack

Сейчас у вас, вероятно, уже есть какое-то Vue-приложение, а если нет, то можете использовать мой репозиторий в качестве отправной точки. Для начала взглянем на структуру наших папок и файлов:

Как видите, структура довольно распространенная, за исключением пары вещей, которые могут бросаться в глаза:

  • есть два отдельных webpack-конфига для клиентского и серверного билдов: webpack.client.config.js и webpack.server.config.js;
  • есть две соответствующие точки входа: client-entry.js и server-entry.js.

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

С клиентским конфигом, вероятно, вы уже имели дело. Он предназначен для сборки нашего приложения в простые JS- и CSS-файлы.

Серверный конфиг более интересен. С его помощью мы создадим специальный json-файл, который будет использоваться на стороне сервера для рендеринга простого HTML-кода нашего Vue-приложения. С этой целью мы используем vue-server-renderer/server-plugin.

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

Как вы могли догадаться, все общие настройки клиентского и серверного конфигов мы вынесли в базовый конфиг.

Шаг 2. Создание точек входа

Перед тем как приступить к созданию клиентской и серверной точек входа в приложение, предлагаю взглянуть на файл app.js:

Обратите внимание: вместо создания экземпляра приложения мы экспортируем фабричную функцию createApp(). Если бы приложение работало только в браузере, то нам не пришлось бы беспокоиться о том, чтобы пользователи получали новый экземпляр Vue для каждого запроса. Но поскольку мы создаем приложение в Node.js процессе, наш код будет инициализирован один раз и останется в памяти того же контекста. Поэтому если мы будем использовать один экземпляр Vue для нескольких запросов, это может привести к ситуации, когда один пользователь получит состояние приложения другого. Чтобы избежать этого, мы должны создавать новый экземпляр приложения для каждого запроса. По этой же причине не рекомендуется использовать синглтоны с состоянием во Vue-приложении.

Каждое приложение, скорее всего, будет иметь какие-то метаданные, например title или description, которые должны отличаться на разных страницах. Вы можете реализовать это с помощью плагина vue-meta. Чтобы узнать, почему мы используем параметр ssrAppId, перейдите по этой ссылке.

В клиентской точке входа мы вызываем createApp(), передавая начальное состояние, установленное сервером, и монтируем приложение в DOM, после того как роутер завершил начальную навигацию. Также в этом файле вы можете импортировать глобальные стили и инициализировать директивы или плагины, которые работают с DOM.

Серверная точка входа в значительной степени описана в комментариях. Единственное, что я хотел бы добавить в отношении коллбэка router.onReady(): если мы используем хук serverPrefetch для предварительного получения данных в каких-то компонентах, он ждет, пока не зарезолвится промис, возвращаемый из хука. Мы увидим пример его использования чуть позже.

Хорошо, теперь мы можем добавить в package.json скрипты для сборки нашего приложения:

Шаг 3. Запуск Express-сервера с Bundle Renderer

Чтобы преобразовать наше приложение в простой HTML на стороне сервера, мы будем использовать модуль vue-server-renderer и файл ./dist/vue-ssr-server-bundle.json, который мы сгенерировали, запустив скрипт build:server. Давайте пока не будем думать о режиме разработки, обсудим это на следующем шаге.

Сначала нам нужно создать рендерер, вызвав метод createBundleRenderer() и передав два аргумента: бандл, сгенерированный нами ранее, и следующие параметры:

  • runInNewContext
  • Помните проблему с общим состоянием между несколькими запросами, которую мы обсуждали на предыдущем шаге? Эта опция решает проблему, но создание нового контекста V8 и повторное построение бандла для каждого запроса является дорогостоящей операцией, поэтому рекомендуется установить этот флаг в значение false из-за возможных проблем с производительностью и остерегаться использования в приложении синглтонов с состоянием.
  • template

Специальный комментарий <!--vue-ssr-outlet--> будет заменен на HTML, сгенерированным рендерером. И кстати, используя опцию template, рендерер автоматически добавит скрипт с объявлением глобальной переменной __INITIAL_STATE__, которую мы используем в client-entry.js при создании своего приложения.

Теперь, когда у нас есть экземпляр рендерера, мы можем сгенерировать HTML, вызвав метод renderToString() и передав начальное состояние и текущий URL для роутера.

Шаг 4. Настройка dev-окружения

Что нам нужно для комфортной разработки Vue-приложения с SSR? Я бы сказал, следующее:

  • запускать только один Node.js сервер без использования дополнительного webpack-dev-server;
  • регенерировать vue-ssr-server-bundle.json файл при каждом изменении исходного кода;
  • hot reloading.

Чтобы реализовать все эти вещи, можно воспользоваться функцией setupDevServer() в server.js файле (см. предыдущий шаг).

Эта функция принимает два аргумента:

  • app — наше Express-приложение;
  • onServerBundleReady() — callback, который вызывается каждый раз при изменении исходного кода и создании нового vue-ssr-server-bundle.json. Он принимает бандл в качестве аргумента.

В файле server.js мы передаем callback onServerBundleReady() в виде стрелочной функции, которая принимает новый бандл и заново создает рендерер.

Обратите внимание: мы рекваерим все зависимости внутри функции setupDevServer(), нам не нужно, чтобы они занимали память процесса в production-моде.

Теперь давайте добавим npm-скрипт для запуска сервера в дев-моде с использованием nodemon:

"dev": "cross-env NODE_ENV=development nodemon ./server.js",

Шаг 5. Использование serverPrefetch()

Скорее всего, вам потребуется получать какие-то данные с сервера во время инициализации приложения. Вы можете сделать это, просто вызвав API-эндпойнт после маунта рутового компонента, но в этом случае ваш пользователь должен будет наблюдать спиннер — не самый лучший UX. Вместо этого мы можем получить данные во время SSR, используя хук компонента serverPrefetch(), который был добавлен в версии 2.6.0 во Vue. Давайте добавим тестовый эндпойнт в наш сервер.

Мы вызовем этот эндпойнт в экшене getUsers. Теперь давайте рассмотрим пример использования хука serverPrefetch() в компоненте:

Как видите, мы используем serverPrefetch() вместе с хуком mounted(). Нам это нужно в тех случаях, когда пользователь переходит на эту страницу с другого роута на стороне клиента, поэтому массив users будет пуст и мы вызываем API.

Также обратите внимание, как определяются title- и description-метаданные для конкретной страницы в свойстве metaInfo, предоставляемом плагином vue-meta.

Ну вот и все. Я думаю, что основные моменты настройки SSR для Vue.js рассмотрены, и надеюсь, что эти шаги помогли вам лучше понять весь процесс.

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному3
LinkedIn



7 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Вопрос — что посоветуете если в проекте используется UI библиотека Vuetify. Как обрабатывать ее скрипты и кучу css?

А если prerender настроить как прокси перед своим сайтом, то в названии статьи можно поставить звёздочку...

Олег спасибо за статью! Скажите что на счет производительности ? Есть какие-то метрики ?

Если под производительностью вы имеете ввиду скорость выполнения SSR, то она будет зависить от сложности приложения и времени выполнения API колов в serverPrefetch.

Коментар порушує правила спільноти і видалений модераторами.

Вопрос — чем такой подход лучше чем nuxt? Сейчас переводим с vue на nuxt, некоторые ништяки прямо из коробки.

Nuxt хорошее решение, но как и любой фреймворк накладывает свои ограничения, например структура папок ru.nuxtjs.org/...​uide/directory-structure , нам это не совсем подходило. В данном решении у вас будет больше контроля над приложением. Также нужно иметь ввиду что nuxt это еще одна зависимость от которой вы вряд ли избавитесь.

Спасибо, но ведь есть:
Готовый к продакшену Vue SSR: 1 простых шагов
Шаг 0: Nuxt.js

Підписатись на коментарі