Запуск в продакшн

Мы написали основной функционал простенького магазина, и теперь пришло время выгрузить его на хостинг.
Админка будет сгенерирована в статичные файлы html, js и css, а вот публичный сайт мы запустим в режиме серверного рендеринга - ssr.
Серверный рендеринг нужен для того, чтобы ваш сайт могли индексировать поисковые машины. Хоть они давно и заявляют о поддержке SPA приложений, но на практике это не очень работает. Да и прочитать те же теги OpenGraph без серверного рендера никак не получится.
Конфигурации будет как для Nginx, так и для Apache2.

Настройки .env

При сборке и генерации проектов используются настройки из файла .env, и наше приложение прежде всего интересует куда ему отправлять запросы:
SITE_URL=http://vesp-shop.test/
API_URL=http://vesp-shop.test/api/
Если вы планируете собирать проект локально и выгружать на сервер - в этом файле нужно прописать правильные url конечного, а не локального, сайта.
Если же вы будете собирать проект сразу на хостинге, то там уже должен лежать .env c корректными настройками и ничего менять не нужно.

Генерация приложений

Первым делом собираем наш проект в статическом виде.
composer node:generate
В результате мы получаем две директории внутри frontend/dist: admin и site. В каждой лежат статические файлы, которые будут отдаваться пользователю веб-сервером.
Html страниц ровно одна - 200.html, больше нам и не нужно. Её задача вывести индикатор загрузки и ссылки на js и css файлы, которые отрисуют наш код.
В принципе, Nuxt может статически генерировать все маршруты, но при нашем подходе они просто не нужны. А так, да, можно статически сгенерировать небольшой сайт, например с документацией, и запустить его без ssr.
Попробуйте ради интереса закомментировать build.exclude в nuxt.config.js и посмотреть, что получится после генерации
Config.generate = {
  cache: false,
  dir: 'dist/admin',
//  exclude: [/^\//],
}

Сборка и запуск сервера приложения

Теперь самое интересное - запуск site в режиме ssr.
Nuxt сам по себе вполне веб-сервер, который запускается из консоли и слушает определённый порт. Вы уже видели это в режиме разработки dev, а теперь нам нужно запустить тот же сервер, но уже в production режиме.
Делается это командой composer node:start, которая соберёт site, а потом запустит менеджер процессов pm2 для управления Nuxt.
Зачем нам менджер процессов? Ну как минимум для того, чтобы следить за состоянием Nuxt, логировать его сообщения и перезапускать, если он упадёт. Конфигурация pm2 находится в файле frontend/ecosystem.config.js:
// Определяем режим работы
const prod = process.argv.includes('production')

module.exports = {
  apps: [
    {
      // Имя приложения
      name: 'site',
      exec_mode: 'cluster',
      // Процесс для работы - наш Nuxt
      script: './node_modules/nuxt/bin/nuxt.js',
      // Аргументы для передачи Nuxt зависят от режима работы
      // Здесь мы прописывааем порт, который будет слушать сервер
      args: (prod ? 'start ' : '') + '--config-file ./src/site/nuxt.config.js -H 127.0.0.1 -p 20001',
      // Количество процессов
      instances: 2,
      autorestart: true,
      max_memory_restart: '1G',
      // рабочий режим - основной
      env_production: {
        NODE_ENV: 'production',
        // Не следить за изменением файов
        watch: false,
      },
      // режим разработки, обычно не используется
      env_development: {
        NODE_ENV: 'development',
        watch: true,
      },
    },
  ],
}
Все настройки можно посмотреть в документации.
Вот, что мы видим после запуска сервера:
В конфигурации прописан порт 20001, значит можно открывать http://127.0.0.1:20001.
Когда сервер запущен, вы можете вызывать ./frontend/node_modules/.bin/pm2 с разными командами, например проверить состояние приложений ./frontend/node_modules/.bin/pm2 status, logs выведет сообщения, а monit запустит своеобразный центр управления.
Остановка сервера производится командой composer node:stop

Деплой на сервер

Итак, у нас есть статически собранная админка, и сайт в двух вариантах: статически собранный и в режиме серверного рендера.
Теперь берём любой хостинг с NodeJS и PHP, например https://modhost.pro, главное, чтобы давали не менее 1 гигабайта свободного места, потому что javascript зависимости занимают прилично мегабайт.
На modhost это получается тариф "Минимальный" за 180 руб.
У меня вот такие настройки Deployment в PhpStorm, через него я и выгружаю свой проект на хостинг:
После выгрузки заходим на сервер через SSH.
У modhost.pro в консоли по умолчанию используется php 7.0, а нам нужен минимум 7.4, так что делаем ссылку на правильную версию:
mkdir ~/bin
ln -s /usr/bin/php7.4 ~/bin/php
source ~/.profile
php -v
В результате должен быть PHP 7.4.27 (cli) (built: Dec 20 2021 21:27:56) ( NTS ) или типа того.
Теперь можно делать
composer install
composer node:install
Дальше переименовываем стандартный файл настроек и редактируем
mv .env.dist .env
nano .env
Я пишу свои настройки, вам нужно указать свои:
APP_NAME="Vesp Framework"

SITE_URL=http://s30069.h10.modhost.pro/
API_URL=http://s30069.h10.modhost.pro/api/

CORS=1

DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_PREFIX=app_
DB_DATABASE=s30069
DB_USERNAME=s30069
DB_PASSWORD=password
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_general_ci
DB_FOREIGN_KEYS=1

JWT_SECRET=secret
JWT_EXPIRE=2592000
JWT_MAX=3

UPLOAD_DIR=/home/s30069/upload/
CACHE_DIR=/home/s30069/tmp/
Теперь можно создать и засеять таблицы
composer db:migrate
composer db:seed
Ну и собрать приложения через composer node:generate
Финальный штрих - запуск pm2 composer node:start
Обратите внимание, что порт 20001 уже может быть занят на сервере, если кто-то другой запустил свой проект на Vesp раньше вас. Придётся указать любой другой свободный порт в frontend/ecosystem.config.js, например 20002, 20003 и т.д.
Все приложения готовы, осталось только указать веб-серверу, как с ними работать.

Настройка Nginx

Вот мой рабочий конфиг на modhost с комментариями
# Отдельная обработка PHP
location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    # Имя обработчика зависит от вашего юзера
    fastcgi_pass backend-s30069;
}

# Эти запросы улетают в API
location ~ ^/(api|__clockwork)/ {
    rewrite ^/(api|__clockwork)/(.*)$ /api.php;
}

# К запросу в админку добавляем слэш на конце
location /admin {
    return 301 /admin/;
}

# Файлы админки обсуживаются прямо оттуда, куда они собираются
location /admin/ {
    root /home/s30069/frontend/dist/;
    # Логировать запросы в админку нам не нужно
    access_log off;
    # Настройки кэширования статики
    gzip on;
    gzip_types text/css application/javascript application/x-javascript text/javascript image/svg+xml;
    expires 1y;
    # Указание страницы по умолчанию
    try_files $uri /admin/200.html;
}

location / {  
    # Серверный режим
    # Передаём все запросы в Nuxt
    proxy_http_version 1.1;
    # Тут лучше ничего не менять
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_redirect off;
    proxy_read_timeout 240s;

    # А вот и адрес нашего сервера
    # Напоминаю, что порт может быть занят - и тогда вы указываете здесь другой
    proxy_pass http://127.0.0.1:20001;

    # Сайт так же можно запустить и в статическом режиме
    # root /home/s30069/frontend/dist/site/;
    # access_log off;
    # gzip on;
    # gzip_types text/css application/javascript application/x-javascript text/javascript image/svg+xml;
    # expires 1y;
    # try_files $uri /200.html;
}
В итоге всё работает как положено:

Настройка Apache2

Файл с примером правил Apache2 лежит по адресу www/.ht.access, но в нём нет настроек для работы ssr.
В отличие от Nginx, я не смог настроить чтение статических файлов из директории выше www, так что вам нужно будет перенести содержимое ~/frontend/dist/site в корень ~/www/, сохранив при этом api.php.
Вот рабочая конфигурация с одного моего проекта:
DirectoryIndex 200.html

RewriteEngine On
RewriteBase /

# Обработка запросов в API
RewriteCond %{REQUEST_URI} ^/(api|__clockwork)/ [NC]
RewriteRule ^(.*)$ api.php [L,QSA]

# Обработка запросов в админку, когда запрошенный адрес не найден
# Это для страниц сайта
RewriteCond %{REQUEST_URI} ^/admin/? [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /admin/200.html [L,QSA]

# Тоже админка, но файл существует - это для скриптов и стилей
RewriteCond %{REQUEST_URI} ^/admin/? [NC]
RewriteRule ^(.*)$ $0 [L,QSA]

# А вот это передача всех оставшихся запросов на сервер Nuxt
# Флаг [P] говорит о проксировании
RewriteCond %{REQUEST_URI} ^/
RewriteRule ^(.*)$ http://127.0.0.1:20001/$1 [P]
Честно говоря, я не большой специалист по Apache2, он мне никогда не нравился, так что конфиг может быть не оптимален. Так же нет 100% уверенности, что все хостинги позволят ему проксировать запросы.
В любом случае, если на хостинге есть возможность настроить Nginx - лучше использовать именно его.

Заключение

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

Обновлено 18 октября 2022

Перенёс рабочий сайт на свой сервер, чтобы не платить деньги за чужой хостинг. Проект работает в статическом режиме, без SSR.
Можете заходить по адресу https://vesp-shop.bezumkin.ru и в админку https://vesp-shop.bezumkin.ru/admin/ (логин и пароль user). Всё открыто только для чтения, изменить ничего нельзя.

46 комментариев

Спасибо за курс! Если не сложно, напиши еще заметку о базовых настройках сайта:
  • как правильно прописать head (с метатегами и каноническими ссылками)
    • как редактировать названия и описания страниц сайта
    • как создать карту сайта
Возможно ты сталкивался с проблемами индексирования сайта, делал все страницы сайта AMP-friendly или применял какие-то другие полезные решения типа nuxt-trailingslash-module (удаляет лишние слэши в конце адресной строки), vue-lazyload ("ленивая" загрузка фото) или cssnano (модуль для сжатия css, устранение дубликатов).
В общем, прошу поделиться своими наблюдениями ))
Василий Наумкин
Честно говоря, мне никогда не нравилась идея затачивать свои сайты под какие-то технологии поисковиков. Что AMP от Google, что Турбо от Яндекс. Так что здесь особо порадовать нечем.
А про всё остальное - читай заключительную заметку курса.
Василий, а как ты решаешь вопрос автозапуска сайтов после перезагрузки сервера?
Отвечаю сам на свой вопрос. Вот ссылка, с описанием настройки автозапуска PM2 при перезапуске сервера https://www.tecmint.com/enable-pm2-to-auto-start-node-js-app/
PM2 будет сам восстанавливать нужные процессы. Сам еще не пробовал, но обязательно попробую настроить.
Василий Наумкин
Так можно делать только на своём сервере.
А на публичном придётся писать скрипт для cron, который будет проверять pm2, и запускать, если не нашёл.
Почему при запуске команды composer node:generate может выбивать ошибку Nuxt Fatal Error ?
Василий Наумкин
Хм, не знаю.
Но я бы попробовал удалить node_modules и поставить зависимости заново, возможно в системе что-то изменилось.
Разобрался. На сервере была установлена древняя нода 10 версии что ли. Обновил - заработало.
Василий Наумкин
Отлично!
Николай Каленников
Коллеги, кто разбирается в настройке сервера. Есть VDS с процессором 3,3 ГГц, 1 Гб оперативки и диск 15 Гб. Поставил Ubuntu 20.04 и FastPanel, NodeJS через терминал. Исходники Vesp копируются,
composer node:install
срабатывает нормально. Но на
composer node:generate
начинаются проблемы. Админка генерируется, а на генерации сайта сначала выводил composer timeout, убрал ограничение по времени, теперь то 137 ошибку выводит, то Killed. Дам полный доступ к серверу, там ничего ценного нет, напишите в телеграме @nikolokol
Василий Наумкин
Killed говорит, что сервер прибивает задачу, скорее всего у провайдера есть какие-то лимиты по нагрузке и их недостаточно для сборки проекта.
Такое запросто может быть на недорогих хостингах.
Александр Наумов
Василий добрый день!
Устанавливаю сайт на хостинг, выполняю команду composer node:generate и получаю ошибку:
composer db:migrate
composer db:seed
Нормально выполнились, база заполнилась.
Василий Наумкин
А ты, случаем, composer node:install не забыл?
Александр Наумов
Запуск composer node:install заканчивается вот такой ошибкой:
Лог файл 2022-10-15T09_58_26_953Z-debug.log очень большой 12000 строк, скачать можно здесь http://s30805.h10.modhost.pro/2022-10-15T09_58_26_953Z-debug.log
Пару раз уже все сносил, все заново переустанавливал и каждый раз лог разный.
Василий Наумкин
У тебя поди свободного места на хостинге не хватает, чтобы установить все зависимости.
Александр Наумов
Василий, спасибо!!!
Сменил тариф с Разработки на Максимальный и вроде все установилось. Изначально выбирал тариф Максимальный, но пояснение под тарифом Разработка меня дезориентировала:
Специальный тариф для разработки сайта. Все параметры как у тарифа "Минимальный", только в 2 раза дешевле и нельзя подключать свои домены.
Александр Наумов
Василий, подскажи, пожалуйста?
Взял из этой статьи конфиг Nginx, заменил в нем s30069 на свой s30805, сменил порт 20001 на 20086. Перехожу на сайт http://s30805.h10.modhost.pro и получаю редиректы:
Хотя по ссылке http://s30805.h10.modhost.pro/info.php сайт работает нормально.
Александр Наумов
Создал два сайта на Modhost на тарифе Тестовый, один на MODX другой пустой. Тот который на MODX открывается нормально, а тот который пустой делает редирект, хотя у обоих конфиг Nginx одинаковый.
Интересно, в чем может быть дело?
Василий Наумкин
Ты в каком режиме-то запускаешь? Если в SSR, то нужно и PM2 запустить, то есть сервер Nuxt для обработки запросов. Именно для этого режима написан конфиг.
Если в статическом, то в конфиге нужно убрать эту часть и раскомментировать другую:
location / {  
    root /home/s30805/frontend/dist/site/;
    access_log off;
    gzip on;
    gzip_types text/css application/javascript application/x-javascript text/javascript image/svg+xml;
    expires 1y;
    try_files $uri /200.html;
}
Админка же у тебя в статическом режиме работает - http://s30805.h10.modhost.pro/admin/
Александр Наумов
Василий, спасибо!! Запускаю в статическом, но все равно редирект происходит. ((
Василий Наумкин
Насколько я вижу - разобрался =)
Александр Наумов
Василий, спасибо! Это у меня, что-то с двумя браузерами не лады были, почему-то они редиректили, а вот Firefox сейчас нормально все отобразил.
Александр Наумов
Видимо это проблема у моего провайдера, пользуюсь Мегафоном через флешку. Прикрутил домен к сайту и все равно редирект происходит на http://m.megafonpro.ru/http-errors/502, сейчас https подключу.
Александр Наумов
Да, с добавлением сайту https, проблема с редиректом решилась.
Василий Наумкин
Интересно, я с таким не сталкивался.
Александр Наумов
Видимо это тренды сегодняшнего времени, Мегафон, что-то химичит. Например, зашел из хрома на 30069.h10.modhost.pro - тестовый сайт на VESP, который ты Василий делал и:
зашел из Фирефокс:
А если прорвешься, то рекламные баннеры на сайт добавят. Хорошо, хоть с https все в порядке.
Василий Наумкин
А сайта на modhost уже и нет - срок вышел, надо деньги платить.
Перенёс на свой сервер, чтобы больше не беспокоиться.
Александр Наумов
Понятно. Василий, не подскажешь, как поменять lang="en" в теге html?
Василий Наумкин
Общая документация по мета-тегам вот здесь, но конкретно по атрибутам тега html там ничего нет.
Но не проблема нагуглить. Добавляй в nuxt.config.js:
Config.head.htmlAttrs = {lang: 'ru'}
Александр Наумов
Спасибо, большое!!!
Александр Наумов
Config.head.htmlAttrs = {lang: 'ru'}
Не понятно почему, но не хочет работать ((
Александр Наумов
Разобрался. У меня в vue файле в теге script прописан head и видимо его наличие там аннулирует настройки head в nuxt.config.js. Прописал там в:
  head: {
    htmlAttrs: {
      lang: 'ru',
    },
и заработало!
Василий Наумкин
Ура!
Первый курс, который я прошел до конца. Ура!
Василий Наумкин
Поздравляю!
Надеюсь, было не очень скучно.
Не скучно
но пора раздел открывать на сайте вопросы по Vesp Вот решил многоязычность скопировать на публичный сайт, а не только чтоб в админке она была. Ошибка Cannot set properties of undefined (setting 'getChoiceIndex') Что нужно и где еще сделать, чтоб она завелась
Василий Наумкин
Vesp на сайте подключается в минимальной комплектации, так что там нет моих добавок для мультиязычности.
Попробуй включить их в конфиге:
пора раздел открывать на сайте вопросы по Vesp
Пока что мы общаемся в комментариях к платному курсу. Собственно, именно поэтому он и платный =)
Александр Наумов
Василий, а админка в статическом режиме будет работать?
Сейчас сайт в статическом режиме, пытаюсь авторизоваться выдает ошибку 500.
Василий Наумкин
Конечно, её вообще нет смысла запускать SSR - никто не индексирует.
Ошибка 500 - это всегда ошибка сервера, то есть бэкенд. Так что смотри логи сервера, дело не во фронтенде.
Александр Наумов
Спасибо большое, пошел разбираться!
Александр Наумов
Ошибку нашел, но не пойму как ее решить:
c07ca73.js:2 Mixed Content: The page at 'https://kamishoff.ru/table' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://s30805.h10.modhost.pro/api/web/products?limit=9&page=1&sort=title&dir=asc'. This request has been blocked; the content must be served over HTTPS.
Файл :
APP_NAME="Vesp Framework"

SITE_URL=https://kamishoff.ru/
API_URL=https://kamishoff.ru/api/

CORS=1

DB_DRIVER=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_PREFIX=app_
DB_DATABASE=s30805
DB_USERNAME=s30805
DB_PASSWORD= *******
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_general_ci
DB_FOREIGN_KEYS=1

JWT_SECRET=secret
JWT_EXPIRE=2592000
JWT_MAX=3

UPLOAD_DIR=/home/s30805/upload/
CACHE_DIR=/home/s30805/tmp/
Конфиг Nginx:
if ($host != 'kamishoff.ru' ) {
    rewrite         ^/(.*)$  https://kamishoff.ru/$1  permanent;
}

if ($scheme = 'http') {
    return    301    https://$host$request_uri;
}

location ~ \.php$ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    # Имя обработчика зависит от вашего юзера
    fastcgi_pass backend-s30805;
}

location ~ ^/(api|__clockwork)/ {
    rewrite ^/(api|__clockwork)/(.*)$ /api.php;
}

location /admin {
    return 301 /admin/;
}

location /admin/ {
    root /home/s30805/frontend/dist/;
    # Логировать запросы в админку нам не нужно
    access_log off;
    # Настройки кэширования статики
    gzip on;
    gzip_types text/css application/javascript application/x-javascript text/javascript image/svg+xml;
    expires 1y;
    # Указание страницы по умолчанию
    try_files $uri /admin/200.html;
}

location / {
    root /home/s30805/frontend/dist/site/;
    access_log off;
    gzip on;
    gzip_types text/css application/javascript application/x-javascript text/javascript image/svg+xml;
    expires 1y;
    try_files $uri /200.html;
}
Откуда он берет s30805.h10.modhost.pro - не пойму?
Так и хочется почистить кэш, как в MODX))
Василий Наумкин
Ошибку нашел, но не пойму как ее решить:
Откуда он берет s30805.h10.modhost.pro - не пойму?
После изменения .env нужно пересобрать фронтенд - API_URL прошивается в него в момент сборки, всё статичное.
Александр Наумов
Спасибо!!! Все заработало!
Александр Наумов
Василий, добрый день!
Столкнулся с проблемой у себя на сайте, а потом ее увидел на твоем сайте, сейчас ее не наблюдаю.
Прохожу на страницу, перезагружаю F5 и вижу сообщение:
Server error An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details.
Не подскажешь, в чем причина и что нужно делать?
Ссылка на анимированную Gif https://vk.cc/cl0Sjy
Василий Наумкин
Ну, какая-то ошибка - смотри логи pm2
У меня такое было раньше, потом ошибку нашёл и исправил, сейчас это можно увидеть только при обновлении сайта и перезапуске pm2.
Александр Наумов
Понял, спасибо!
bezumkin.ru
Personal website of Vasily Naumkin
Прямой эфир
Александр Наумов
23.07.2024, 00:20:37
Василий, спасибо большое!!
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.
Василий Наумкин
22.01.2024, 07:43:20
Давай-давай!
Василий Наумкин
24.12.2023, 14:26:13
Спасибо!