Перейти до основного вмісту
Назад до нотаток

Розгортання React і Vite сайту на Nginx із версіями релізів

Still Brewing UAОпубліковано: Оновлено:

Статичному сайту не потрібен Node.js на сервері. Потрібна тека з файлами, правильні права та спосіб змінити версію так, щоб не було моменту, коли сайт оновлений наполовину. Ось процес, яким я користуюся, і причини кожного кроку.

Збирайте локально, публікуйте результат

Vite перетворює проєкт на теку зі звичайними файлами: HTML, бандли JavaScript і CSS з хешами в назвах та статичні ресурси. Ця тека і є весь сайт. Збирання на своїй машині лишає сервер без інструментів, які працювали б лише кілька секунд на день.

Збірка з production-адресою сайту
VITE_SITE_URL=https://example.com npm run build

Адреса сайту — значення часу збірки. Vite вбудовує її в бандл і в кожен абсолютний URL на сторінках: canonical, Open Graph, sitemap. Передача через командний рядок повністю тримає production-значення поза репозиторієм: нічого не треба редагувати перед збіркою й нічого зайвого не потрапляє в коміт.

Завантажуйте лише результат збірки

На сервер потрапляє тільки вміст теки збірки. Вихідний код, історія Git, тести, lock-файли та залежності лишаються на вашій машині. Менше файлів на сервері — менше що захищати, менше ризику віддати зайве й помітно швидше завантаження.

Саме тому не потрібне жодне середовище виконання. Nginx читає файли з диска й віддає їх. Немає процесу, який треба тримати живим, немає порту для резервування й нічого перезапускати, коли змінюється вміст.

Окремий каталог для кожного релізу

Кожне розгортання йде у власний каталог, названий коротким хешем коміту, з якого його зібрано. Така назва дає відповідь на питання «що зараз працює?» однією командою, а повернення назад зводиться до вказівки на каталог, який уже існує.

Структура на сервері
/var/www/example/
├── releases/
│   ├── <previous-release-id>/
│   └── <release-id>/
└── current -> releases/<release-id>

Спершу завантажуйте у тимчасовий каталог у домашній теці, перевіряйте там і лише потім копіюйте на місце. Якщо передавання перерветься, неповна копія ніколи не потрапить у каталог, який читає вебсервер.

Копіювання результату збірки
ssh -i /path/to/key user@203.0.113.10 'mkdir -m 700 ~/upload-<release-id>'
scp -r -i /path/to/key ./dist/. user@203.0.113.10:~/upload-<release-id>/

Перевірте завантаження до публікації

Кількість файлів і розміри можуть збігатися, а вміст — відрізнятися. Порівняння контрольної суми кожного файлу з обох боків швидке й повністю знімає сумніви.

Порівняння контрольних сум
# локально
(cd dist && find . -type f | sort | xargs sha256sum) > local.txt

# на сервері
ssh -i /path/to/key user@203.0.113.10 \
  'cd ~/upload-<release-id> && find . -type f | sort | xargs sha256sum' > remote.txt

diff local.txt remote.txt && echo "однакові"

Якщо списки відрізняються лише роздільником між хешем і назвою файлу — це різниця у форматуванні між системами, а не у вмісті. Перш ніж вважати, що щось пішло не так, порівняйте самі хеші.

Власник і права

Файли належать root і недоступні вебсерверу для запису. Nginx потрібно лише читати їх. Каталогам потрібен біт виконання, щоб у них можна було заходити; звичайним файлам — ні.

Безпечні права
sudo chown -R root:root /var/www/example/releases/<release-id>
sudo find /var/www/example/releases/<release-id> -type d -exec chmod 755 {} +
sudo find /var/www/example/releases/<release-id> -type f -exec chmod 644 {} +

Нічого не має бути доступним для запису всім. Швидка перевірка після кожного розгортання ловить випадковий режим доступу, доки він не став звичкою.

Перевірка
sudo find /var/www/example/releases/<release-id> -perm /o=w

Перемикайте версії атомарно

Корінь у Nginx указує на symlink, а не на каталог релізу. Розгортання — це переміщення цього symlink. Якщо робити необережно (видалити, потім створити), виникає коротка мить, коли кореня не існує й відвідувачі отримують помилку.

Створення нового посилання під тимчасовою назвою та перейменування його поверх старого прибирає цей проміжок. Перейменування в межах однієї файлової системи атомарне: кожен запит бачить або старий реліз, або новий, але ніколи — порожнечу.

Атомарне перемикання
sudo ln -sfn /var/www/example/releases/<release-id> /var/www/example/current.new
sudo mv -T /var/www/example/current.new /var/www/example/current

Оскільки конфігурація не змінилася, Nginx перезавантажувати не потрібно. Він розв’язує symlink на кожен запит і одразу бачить нову ціль. Reload потрібен лише тоді, коли ви справді редагували файл конфігурації.

Зберігайте попередній реліз

Старий каталог лишається точно там, де був. Він коштує кілька мегабайтів і є всім планом відкату. Видалення старих релізів — окреме свідоме прибирання, а не частина розгортання.

Перевіряйте те, що справді віддається

Перевіряйте живі адреси через HTTPS, а не файли на диску. Переконайтеся, що потрібні сторінки повертають 200, редиректи ведуть куди слід, а неіснуюча адреса повертає справжній 404, а не головну сторінку.

Швидка перевірка
curl -I https://example.com/en/
curl -I https://example.com/uk/
curl -I https://example.com/sitemap.xml
curl -I https://example.com/does-not-exist   # очікуємо 404

Справжній 404 важливіший, ніж здається. Якщо невідомі шляхи тихо віддають головну сторінку, пошукові системи індексують нескінченні її дублікати, а биті посилання лишаються для вас невидимими.

Одна пастка, про яку варто знати: коли HTTP уже перенаправляє на HTTPS, запит на порт 80 повертає короткий текст редиректу, а не вашу сторінку. Перевірка через заголовок Host на localhost і порт 80 виглядатиме як невдале розгортання, хоча воно вдалося. Перевіряйте через HTTPS.

Відкат

Відкат — це та сама команда розгортання з ідентифікатором попереднього релізу. Той самий механізм, та сама атомарність, без зміни конфігурації та без reload.

Повернення до попереднього релізу
sudo ln -sfn /var/www/example/releases/<previous-release-id> /var/www/example/current.new
sudo mv -T /var/www/example/current.new /var/www/example/current

Запишіть точну команду до розгортання, а не після того, як щось зламалося. Відкат, який доводиться відновлювати з пам’яті під тиском, — це не відкат.

Потрібно зробити щось подібне?

Ці нотатки — з роботи, яку я вже запустив. Якщо потрібне те саме й зроблене як слід, розкажіть, що ви задумали.