logo
Web
Максим Колмогоров
Максим Колмогоров
SEO

Интегрируем SSR в Laravel 9 + Vue 3 + Vite проект

Большую массу PHP-программистов пугает тема SSR, ведь данная технология сильно сопряжена с JavaScript. Многие программисты рекомендуют использовать Node.js и готовые SSR фреймворки в обход любимому PHP. И да, справедливости ради, уйти от PHP в данном случае и правда хороший вариант, хотя не все хотят это делать по каким-либо причинам. Так вот, данная статья призвана показать, как с помощью PHP и Node.js, внутри Laravel, реализовать полноценный SSR как в каком-нибудь готовом Node.js фреймворке по типу Nuxt.js или Quasar. Сходите за чаем, будет весело (автору статьи).

Некоторые моменты будут показаны очень упрощено и, возможно, неказисто. Сделано это для упрощения статьи, но если захотите что-то обсудить или спросить, смело пишите свои комментарии.

Интегрируем SSR в Laravel 9 + Vue 3 + Vite проект

Что такое SSR

SSR – это технология, которая позволяет исполнять на сервере JavaScript, а не в браузере, как это обычно происходит. SSR легко реализуется с помощью Node.js, так как там внутри есть все необходимые инструменты. Можно, конечно, обойтись без Node.js и вырвать из его исходников V8 (движок JavaScript на С++), но это уже совсем безбашенный вариант.

Если интересно почитать про SSR подробнее, то переходите по ссылке на нашу тематическую статью.

Перед стартом

Автор надеется что у Вас глобально установлен composer, PHP и Node.js. Автор использует PHP 8.1.1, Composer 2.4.4, Node 16.15.0. В статье используется Laravel 9, Vue 3 и Vite 3.0.0.

Подготовка

Для начала, давайте создадим Laravel проект:

composer create-project laravel/laravel example-app

Теперь мы можем перейти в проект:

cd example-app

И запустим его с помощью команды:

php artisan serve

Хотя, запускать его нам пока не надо, но можете это сделать для проверки.

Теперь нам надо установить laravel/ui пакет, для того, чтобы потом добавить Vue.js в проект. Выполните команду:

composer require laravel/ui

После этого нам надо добавить, через artisan, уже непосредственно Vue.js:

php artisan ui vue

Скорее всего, у Вас проект теперь использует Vue 3, а не Vue 2. А также Vite, а не Webpack, но это все не страшно, мы были к этому готовы. У нас будет Vue 3, а значит и возможность использовать composition API.

Теперь смотрите очень внимательно и выполняйте все действия правильно. Сначала установим пакеты, которые прилетели к нам в проект после установки прошлого пакета из composer. Для этого выполните команду:

npm install

Теперь устанавливаем Vue Router:

npm install vue-router@4

Далее, нам надо создать что-то вроде простого базового проекта на Vue.js. Для этого поместите следующее содержимое в resources/views/welcome.blade.php (старое удалите):

Создайте в папке /resources/js файл router.js со следующим содержимым:

Создайте в папке /resources/js папку pages и внутри нее два компонента Home.vue и About.vue с таким содержимым:

Теперь, создайте в папке /resources/js App.vue с таким содержимым:

Тут как раз мы уже используем подход с setup, а значит можем использовать composition API (ну это так, для справки).

И последний шаг, в папке /resources/js есть файл app.js, замените его содержимое на это:

Когда Вы все сделали, запускайте команду:

npm run build

Потом, запускайте сервер с помощью:

php artisan serve

В браузере у Вас должен запуститься наш проект по адресу localhost:8000.

пример laravel 9 с vue 3

Можете посмотреть как работает router, нажимать на кнопку с переменной count. В целом этого хватит, теперь можно настраивать SSR.

Ах да, если Вам не хватает привычной команды watch, можно добавить в раздел scripts в package.json свою команду с подобным эффектом:

vite build --watch

Пример:

пример package.json

Добавляем SSR

Если Вы откроете исходный код нашего проекта сейчас, то не увидите текста из компонентов:

laravel 9 и vue 3 без SSR

Пора исправлять это гнусное недоразумение. Для этого мы напишем свой модуль на Node.js, который будет делать SSR. После этого из PHP мы будем вызывать Node.js и делать магию (надеемся чай уже выпит, далее потребуется что-то покрепче).

В папке /resources/js создайте файл server-app.js с таким содержимым:

Это копия нашего файла app.js, только настроенная под исполнение на сервере. Вместо createApp мы используем createSSRApp из библиотеки Vue.js. Функция renderToString из vue/server-renderer – это готовая функция, которая будет рендерить наше приложение на сервере. Подробнее можете почитать тут, в документации Vue.js по SSR.

A в асинхронную функцию main мы все обернули для того, чтобы использовать await в router и renderToString. Это обязательно, если router не отработает (не будет готов), то Вы никогда не отрисуете нужный компонент по нужному маршруту. А если не дождетесь ответа от renderToString, то и не увидите готовое приложение. И да, в нашем случае функция main автоматически вызывается, когда будет обращение к файлу.

Обратите внимание на строчку 9, там есть переменная requestUrl, которую мы заполняем из process.argv. Переменная процесса всегда доступна в Node.js, оттуда можно взять полезные вещи. Например, когда мы делаем так:

node index.js test

В process.argv[2] будет лежать test. Вот таким же способом из PHP мы будем передавать URL адрес, на которой совершился переход.

Ну и на 19 строчке есть process.stdout.write(), таким способом, мы отдадим результат из скрипта, который вызовем из shell_exec() в PHP. К слову, можно просто console.log() сделать в Node.js, тогда PHP тоже увидит результат.

Теперь, нам надо внести правки в router.js, который мы сделали пару минут назад. Измените его содержимое на следующее:

Здесь мы добавили изменения в начале файла – на 1, 3 и 4 строчке. Мы проверяем выполняется ли сейчас код на сервере, и если ответ утвердительный, то вместо createWebHistory используем createMemoryHistory. То есть, храним нужные маршруты в оперативной памяти, а не в истории браузера.

Вообще, внутри Node.js нет переменных window и document. Вместо window есть global, хотя ее содержимое даже близко к window не стоит. Ну а последней – document, нет и в помине, ибо это переменная дает доступ к API DOM дерева. Как Вы уже догадались, DOM дерева в Node.js нет.

Теперь идем в корень проекта, и в файле vite.config.js убираем строку:

vue: 'vue/dist/vue.esm-bundler.js',

К сожалению, это какое-то странное поведение Vite 3 и Vue 3, если Вы не уберете эту строчку, то Vite не увидит библиотеку vue/server-renderer. К слову, собрать приложение дальше нам это не помешает.

Теперь, там же, добавьте две строчки в раздел laravel() под переменной ​​input:

ssr: 'resources/js/server-app.js',
ssrOutputDirectory: 'bootstrap/ssr',

Должно получиться так:

Корректируем vite.config.js

Переменная ssrOutputDirectory по умолчанию и так все отправит в bootstrap/ssr, но если Вы захотите, можете переопределить папку куда полетят собранные файлы (но пока это не делайте). Теперь модернизируем команду build в package.json. Давайте добавим туда сборку SSR сервера:

дорабатываем package.json

vite build && vite build --ssr

Теперь нам осталось модернизировать welcome.blade.php и web.php (пути в Laravel). Добавьте этот код в routes/web.php (прошлый можете удалить):

И в resources/views/welcome.blade.php удалите секцию:

<div id="app"></div>

И добавьте следующий код:

Должно получиться как-то так:

как должно получиться

Строку:

/Users/maximkolmogorov/.nvm/versions/node/v16.15.0/bin/node

Измените на свой путь до Node.js. Вообще, обычно это:

/usr/local/bin/node

Но у автора по этому пути лежит Node.js 12, а нужно не меньше 16.15. Если что, можете установить нужную версию через NVM. Узнать версию Node.js у себя можно с помощью:

node -v

А путь до нее:

npm config get prefix

Главное к результату добавьте в конце /bin/node.

Как видно по коду, мы запускаем Node.js сервер из PHP с помощью shell_exec(). Почему именно shell_exec, а не exec? Просто exec вернет только последнюю строку вывода, а shell_exec вернет все. Хотя, внутри Node.js мы возвращаем все через process.stdout.write(), и результат и так будет в одной большой строке, но если бы мы где-то выше использовали еще и console.log() (то есть, создали бы две строки), то shell_exec наш обязательный выбор.

Как видите, вторым аргументом мы передаем наш собранный SSR сервер, а третий аргумент – это URL адрес, который мы пихаем в router.js. Главное в web.php дублировать все маршруты, которые есть в нашем Vue.js приложении.

Теперь собираем все наше добро командой:

npm run build

И запускаем наш Laravel проект:

php artisan serve

Переходим на какую-нибудь страницу, открываем полный исходный код и видим наш долгожданный контент:

laravel 9 + vue 3 + ssr

Итог

Думаем слова излишни. Если Вы дочитали до этого момента (не перематывая), то, скорее всего, передумали внедрять SSR таким способом. Все таки, это довольно болезненный процесс, при котором надо неплохо владеть PHP, Node.js, инструментом сборки и фреймворком, который нужно рендерить (в нашем случае Vue.js). Автор статьи настоятельно рекомендует использовать готовые SSR фреймворки на базе Node.js: Nuxt.js, Quasar – для Vue.js, Next.js, Gatsby – для React.

Под капотом у готовых инструментов для SSR в PHP (по типу spatie/laravel-server-side-rendering) внутри лежит подобная реализация и подход к работе с Node.js сервером и рендерингом контента. Вам в любом случае придется писать Node.js сервер. Ну и данный плагин не протестирован вместе с Vite, у автора статьи не получилось подружить его вместе с ним. Видимо, если захочется использовать его, придется мигрировать с Vite на Webpack в рамках Laravel. А новые версии Laravel идут вместе с Vite, и выбора между ним и Webpack пока не предусмотрено. Хотя, в том же Vue CLI можно выбирать.

минусы ssr в laravel

Ну и напоследок, Вы же понимаете, что если отойти от реализации автора и придумать что-то другое, где программист будет в нескольких разных местах (в рамках одного PHP процесса) вызывать shell_exec(), то это чревато потерей производительности. Посмотрите на картинку выше. Все таки, SSR крайне тяжелая операция, которую лучше не использовать больше одного раза и не трогать лишний раз цепочку Node.js > V8 > HTML в одном проекте при выполнении PHP скрипта.

Спасибо за внимание, если понравилось… то круто. Хорошего дня!

SSR
Поможем внедрить SSR в любой JavaScript проект
Нажимая на кнопку, вы даете согласие на обработку персональных данных и соглашаетесь c политикой конфиденциальности

Оставьте комментарий

Нажимая на кнопку, вы даете согласие на обработку персональных данных и соглашаетесь c политикой конфиденциальности

Комментариев: 2

Куку
13.08.2023 15:42:16
вроде идея норм, попробовал, изначальное всё рендерится, но если у тебя сторы типа pinia или vuex всё ломается, гидрация не происходит, если там fetch запросы на странице, этого сильно нехватает в данном гайде кмк (понятно что это отдельный вопрос) плюс получается мы всегда будем рендерить серверные страницы, а как же суть SPA, короче гайд для супер простого не особого рабочего сайта
Максим Колмогоров
28.08.2023 14:10:58Автор
Ну в целом если сайт простой, то и этого гайда хватит. Насчет сути SPA: не видим проблем, реактивность остается. Ну а если есть запросы внутри компонентов, то тут так быстро и не объяснишь, это тянет на отдельную статью. Но если кратко, то нам надо либо разработать свою логику похожую на функционал asyncData в Nuxt.js, или нам надо переделать сам подход получения данных внутри компонентов и отказаться от него в пользую подгрузки всех данных заранее в PHP. И да, имеется ввиду только те данные, которые нужно желательно получить до отрисовки компонентов, запросы на событиях можно не трогать.
Андрей
08.12.2023 23:04:08
Жесть, очень неприятный способ, но спасибо, я хоть понял в каком направлении двигаться!