Всем доброго времени суток. Сегодня поговорим про великий и ужасный Server-side rendering. Недавно я задался вопросом как внедрить Vue SSR в уже существующий проект на Vue.js и чтобы при этом было удобно вести разработку. Сделать Vue SSR задача довольно нетривиальная. Пришлось прочитать не только официальную документацию, но и перелопатить все существующие статьи которые были в Google на тот момент. Результат, который у нас получится вы можете посмотреть в репозитории на GitHub.

В статье мы сделаем следующее:

  • добавим SSR в проект,
  • разберемся с плагином Vue meta для того, чтобы мы могли управлять метаданными нашего приложения,
  • запустим production сервер на Express.js,
  • настроим dev-окружение c hot module replacement для удобной разработки

1. Отличия  разработки с Vue SSR

Для начала хотелось бы поговорить чем отличается разработка приложения Vue SSR от простого Vue приложения. Во первых для SSR требуется две конфигурации для Webpack — одна для клиента, другая для сервера. Так же важным отличием является то, что каждый раз когда пользователь запрашивает у сервера экземпляр приложения, мы должны отдавать свежий экземпляр передавая в этот экземпляр контекст запроса чтобы не происходило загрязнение состояний приложения. Если мы будем отдавать экземпляр приложения без контекста, то сессии пользователей могут перепутаться. Для решения этой задачи нам нужно создавать не экземпляр приложения, а функцию-фабрику, которую можно вызывать каждый раз для создания нового экземпляра передавая в нее контекст запроса.

2. Структура проекта

Посмотрим на структуру проекта.

Структура проекта Vue SSR

Структура проекта Vue SSR

Структура файлов и папок довольно распространенная. В глаза бросаются три webpack конфигурации, о которых мы поговорим ниже. Так же можно увидеть файлы entry-client.js и entry-server.js — это точки входа конфигураций webpack для клиента и сервера соответственно.

3. Конфигурации Webpack для клиента и сервера

Начнем с конфигурации для клиента. Здесь нет ничего необычного, за исключением того, что мы вынесли общую часть из двух конфигураций webpack отдельно и назвали её webpack.base.config.js. Для того чтобы использовать base-конфиг в конфигурациях для клиента и сервера нам понадобится плагин 'webpack-merge'.

В этой конфигурации настройки все стандартные, мы лишь указали точку входа для клиента entry-client.js.

Теперь посмотрим на конфигурацию для сервера. На выходе этой конфигурации мы получим  JSON файл, с помощью которого и будет генерироваться статичный HTML для сервера. Для этого мы воспользовались плагином vue-server-renderer/server-plugin. Заметьте, что плагин vue-server-renderer должен быть такой же версии, что и vue.

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

Если вы хотите вынести css-стили в отдельный файл, то используйте плагин 'mini-css-extract-plugin'.

4. Разбираемся с точками входа

Для начал разберемся с файлом где создается наше приложение — app.js.

Как мы и говорили в начале статьи, мы экспортируем функцию-фабрику createApp() и передаем в нее context, для того чтобы каждый раз при запросе получать с сервера новый экземпляр  Vue в контексте запроса. Для маршрутизатора как и для createApp нужно тоже каждый раз создавать новый экземпляр роутера. Тоже самое касается и хранилища на vuex. В нашем примере vuex не используется. Также мы объявили использование плагина vue-meta и указали дополнительную опцию ssrAppId: 1. В ней мы указали id приложения для приложения, которое получается на сервере после рендеринга. Более подробно для чего нужен этот параметр можно почитать здесь.

Клиентская точка входа:

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

Точка входа для сервера:

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

Теперь допишем скрипты в файл package.json для запуска сборки для сервера и для клиента и запустим их.

О NODE_ENV и переменных сред вы узнаете из этой статьи — NODE ENV в Node JS или что такое переменные окружения.

Если вы всё сделали правильно, то после запуска этих скриптов в папке dist у вас появятся два файла: main.js  после запуска клиентской конфигурации и vue-ssr-server-bundle.json после запуска серверной.

5. Пишем конфигурацию сервера с Vue SSR

Теперь мы детально разберем конфигурацию для сервера на Express.js

Здесь нам потребуется пакет vue-server-renderer, а именно функция createBundleRenderer из этого пакета. Она будет принимать bundle — это как раз и есть тот json-файл, который получился в процессе сборки серверной части. Так же функция принимает объект настроек:  runInNewContext: false — эта опция означает, что код сборки будет выполнятся в том же контексте, что и серверный процесс; template — это наш index.html модифицированный для Vue SSR.

Далее мы запускаем функцию createRenderer, в качестве параметра bundle мы передаём ей json файл серверной сборки и записываем результат в переменную renderer. На строке 18 мы говорим, чтобы express брал статические ресурсы из папки dist. Затем идет обработка запросов на сервер. Символ '*' говорит о том,  что мы будем обрабатывать любой запрос, который поступит на сервер. Мы создаем переменную context и присваиваем ей объект, в который записываем url запроса если он есть. На строке 28 мы рендерим наш экземпляр приложения в строку при помощи метода renderToString и если все прошло успешно, то передаем отрендеренный экземпляр приложения в ответ.

Теперь запустим наш сервер! Добавим в package.json следующий скрипт.

Этот скрипт будет обращаться к файлу server.js и запускать сервер. После запуска сайт будет доступен по адресу http://localhost:5000

Если вы сделали всё правильно, то увидите в браузере следующее.

Результат Vue SSR

6. index.html для Vue SSR

Отдельно хотелось бы взглянуть на файл index.html, который мы подготовили для SSR.

На строчках 4 и 5 будет вставляться тайтл и другая мета-информация, которую мы оставляли в компонентах. Так же здесь присутствует специальный комментарий <!--vue-ssr-outlet--> — он будет заменен на отрендеренную разметку.

7. Настраиваем dev-окружение с Vue.js SSR и HMR

Теперь мы настроим dev-окружение, с которым будет удобно работать, при этом у нас  будет поддержка Vue SSR и HMR (hot-module replacement). В этом нам помогут два пакета webpack-dev-middleware и webpack-hot-middleware. Устанавливаем их из npm. Затем нам нужно немного модифицировать файлы server.js и клиентскую конфигурацию webpack.

Ели мы запустим сервер при NODE_ENV=’development’, то у нас запуститься dev-сервер. Он будет доступен по тому же порту, что и production-сервер.

А в файле клиентской конфигурации немного меняем точку входа.

Теперь если переменная среды NODE_ENV будет равна development, то в массив точек входа мы добавляем точку входа, которая отвечает за HMR и при изменении какого-либо компонента, этот компонент будет сразу изменятся в браузере без перезагрузки окна. Более подробно с этими расширениями вы можете ознакомиться по этим ссылкам: webpack-dev-middleware,  webpack-hot-middleware.

Заключение

Таким образом, из этой статьи мы узнали как внедрить Vue SSR в готовый проект. Надеюсь, данный материал был вам полезен. Напомним, результат вы можете посмотреть на GitHub.