Готовый к продакшену 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 рассмотрены, и надеюсь, что эти шаги помогли вам лучше понять весь процесс.

Похожие статьи:
Привіт! Я QA Team Lead із семирічним досвідом роботи в галузі контролю якості, п’ять років з яких залучена до управління QA-командами....
From the fireplace to the stove, from the barbecue to the oven, cast iron is the ideal conductor of heat. Find out how to clean cast iron with our tips! Cast iron is a ferrous alloy with high carbon content. Compared to mild steel...
Оператор мобильной связи Tele2 объявил о внедрении технологии HD-Voice в сетях 3G. Активация нового стандарта кодирования...
GitHub анонсував, що впроваджує двофакторну автентифікацію для усіх розробників, які пишуть код у будь-якому проєкті...
Наразі в застосунку «Резерв+» кількість IT-вакансій у Силах оборони складає 132. Про це DOU повідомили...
Яндекс.Метрика