0% прочитано

Vite environment variables у Laravel deployment: чому server .env було недостатньо

На сервері всі VITE_REVERB_* значення були в .env, але production bundle усе одно збирався без них. Розбираю, як я знайшла причину в GitLab CI build stage, перенесла build-time variables у правильний environment scope і додала fail-fast перевірки перед deployment.

28 серпня 2026 р. 7 хв читанняCI/CD

Під час production deployment Laravel-застосунку я отримала дивну ситуацію: усі VITE_REVERB_* variables були в server .env, але frontend усе одно падав із помилкою Pusher.

Спочатку я очікувала, що достатньо перевірити .env на сервері. Значення там справді були.

Проблема виявилася в іншому місці: Vite bundle збирався не на production server, а раніше - всередині GitLab CI Runner.

Server .env був правильним. Але на момент production deployment frontend bundle уже був зібраний без потрібних VITE_* values.

Це четверта частина серії. У попередній статті я розбирала Inertia SSR case, де лише 404 page стабільно відтворювала помилку. Під час тієї ж integration роботи окремо проявилася ще одна проблема - уже не SSR, а build-time environment.

Помилка, з якої все почалося

Після deployment application відкривався, routing уже працював, але browser console показувала Pusher error.

Це виглядало як проблема Reverb configuration. Тому першою думкою було перевірити production .env і переконатися, що APP KEY, HOST, PORT та SCHEME на місці.

Вони були на місці.

Browser error - missing Pusher app key
You must pass your app key when you instantiate Pusher.

Server .env виглядав правильно

Я окремо перевірила на сервері, що всі потрібні Reverb та Vite variables справді задані, не виводячи їхні значення в log.

BASH - Перевірка Reverb variables на сервері
for key in \  REVERB_APP_KEY \  REVERB_HOST \  REVERB_PORT \  REVERB_SCHEME \  VITE_REVERB_APP_KEY \  VITE_REVERB_HOST \  VITE_REVERB_PORT \  VITE_REVERB_SCHEMEdo    value=$(grep -m1 "^${key}=" .env | cut -d= -f2-)     if [ -n "$value" ]; then        echo "$key=SET"    else        echo "$key=EMPTY_OR_MISSING"    fidone
Для публічної статті я не показую реальні environment values. Для debugging достатньо знати, що variable існує; secrets у screenshots, logs або прикладах не потрібні.

Ключова деталь: Vite потрібні values під час build

Frontend читав Reverb settings через import.meta.env.

Це означало, що потрібні VITE_* values мали бути доступні саме тому process, який виконував npm run build. У моєму pipeline цей process працював усередині GitLab Runner.

Production .env на сервері з’являвся в зовсім іншому середовищі й уже не міг змінити готовий JavaScript bundle.

JavaScript - Reverb configuration у frontend
window.Echo = new Echo({    broadcaster: 'reverb',    key: import.meta.env.VITE_REVERB_APP_KEY,    wsHost: import.meta.env.VITE_REVERB_HOST ?? window.location.hostname,    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'http') === 'https',});

Правильний .env на сервері не допоможе frontend bundle, якщо bundle був зібраний раніше в іншому середовищі.

Я перевірила, де насправді виконується build

Наступною я відкрила .gitlab-ci.yml і пройшла deployment flow по порядку.

Там і знайшлася причина. npm ci та npm run build виконувалися в GitLab Runner, після чого вже готові public/build і SSR artifacts копіювалися на сервер.

Тобто production server отримував результат build, а не виконував цей frontend build зі своїм .env.

YAML - спрощений deployment flow
script:  - npm ci  - npm run build   # generated artifacts are transferred afterwards  - rsync public/build/ ...  - rsync bootstrap/ssr/ ...
До

GitLab Runner

npm run build

VITE_* missing

broken frontend bundle

production server

Після

GitLab Runner

production-scoped VITE_*

npm run build

correct frontend bundle

production server

Рішення було в GitLab CI variables, а не в server .env

Після цього я перестала шукати проблему в production .env.

VITE_* variables, потрібні для frontend build, я додала в GitLab CI/CD Variables. Для development та production зробила окремі environment scopes, щоб кожен deployment отримував свої values.

Server-side REVERB_* configuration при цьому залишилася відповідальністю server environment.

Variable

Де потрібна

Scope

VITE_REVERB_APP_KEY

Vite frontend build

development / production

VITE_REVERB_HOST

Vite frontend build

development / production

VITE_REVERB_PORT

Vite frontend build

development / production

VITE_REVERB_PORT

Vite frontend build

development / production

REVERB_APP_SECRET

Laravel / Reverb server

server only

Не можна переносити server secrets у VITE_* variables. Усе, що використовується як VITE_*, може потрапити у client bundle. REVERB_APP_SECRET повинен залишатися тільки на server side.

Development і production я розділила через environment scope

Мені не хотілося мати один глобальний набір VITE_REVERB_* values для всіх deployments.

Тому для кожного key я створила окремий запис для development і окремий для production. Так один і той самий pipeline може збирати frontend з правильним host, port, scheme та app key для конкретного environment.

Це також зменшує ризик випадково зібрати production bundle з dev configuration.

  • VITE_REVERB_APP_KEY → development

  • VITE_REVERB_APP_KEY → production

  • VITE_REVERB_HOST → development

  • VITE_REVERB_HOST → production

  • VITE_REVERB_PORT → development

  • VITE_REVERB_PORT → production

  • VITE_REVERB_SCHEME → development

  • VITE_REVERB_SCHEME → production

Environment scope для build-time variables я тепер вважаю частиною deployment configuration, а не просто зручним способом організувати GitLab Variables.

Я додала fail-fast перевірки

Після цього я не хотіла залишати ситуацію, де variable знову випадково видалять, а pipeline все одно збере broken bundle.

Тому перед build я додала прості shell checks. Якщо хоча б одного required value немає, deployment падає одразу.

Для мене це набагато краще, ніж успішний pipeline із JavaScript bundle, який ламається вже в browser.

YAML - fail-fast environment checks
script:  - ': "${VITE_INERTIA_SSR_PORT:?VITE_INERTIA_SSR_PORT is required}"'  - ': "${VITE_REVERB_APP_KEY:?VITE_REVERB_APP_KEY is required}"'  - ': "${VITE_REVERB_HOST:?VITE_REVERB_HOST is required}"'  - ': "${VITE_REVERB_PORT:?VITE_REVERB_PORT is required}"'  - ': "${VITE_REVERB_SCHEME:?VITE_REVERB_SCHEME is required}"'  - ': "${HEALTHCHECK_URL:?HEALTHCHECK_URL is required}"'  - npm ci  - npm run build

Ці перевірки не виправляють configuration. Вони роблять неправильну configuration неможливою для тихого deployment.

Чому fail-fast тут важливіший за fallback

У frontend configuration уже були fallback values для host, port та scheme. Але fallback не підходить для всього.

Якщо VITE_REVERB_APP_KEY відсутній, browser не може сам здогадатися, який key потрібно використати. Саме тому Pusher error був корисним симптомом, а не місцем, де варто було додавати ще один workaround.

Я вирішила краще зупиняти build, ніж маскувати відсутню required variable.

Якщо без variable application не може працювати правильно, pipeline повинен сказати про це до deployment.

Першими я додала environment-scoped variables для development і запустила звичайний deploy.

Pipeline пройшов fail-fast checks, frontend та SSR bundles зібралися, а після hard reload Pusher error у browser зник.

Тільки після цього я повторила той самий setup для production scope.

Після нового development build помилка “You must pass your app key when you instantiate Pusher” більше не відтворювалася.

Перед production я перевірила variables окремо

Перед merge у main я окремо додала ті самі чотири VITE_REVERB_* keys із production scope та production values.

Це був важливий момент: development configuration уже працювала, але production не повинна була випадково успадковувати dev values.

Після цього production deployment пройшов build, Composer update, cache rebuild і restart потрібних Supervisor processes.

Production deployment - final status
Worker status: RUNNINGReverb status: RUNNINGSSR status: RUNNINGSupervisor processes are running. Deployment completed successfully.Job succeeded

Після deployment я перевірила не тільки pipeline

Green GitLab job для мене ще не означав, що перевірка завершена.

Я окремо перевірила production revision, Inertia SSR process, Supervisor services, public UK/EN navigation та admin. Browser console після smoke test залишилася чистою.

Потім я поставила marker у Laravel log і перевірила тільки записи, які могли з’явитися після smoke test.

BASH - Перевірка Inertia SSR
php artisan inertia:check-ssr
Результат - Inertia SSR
Inertia SSR server is running.
BASH - Перевірка Supervisor processes
sudo supervisorctl status | grep '^icanup'

Після production smoke test у browser не було жодної console error, а після контрольної log marker не з’явилося жодного нового Laravel error.

Production bundle уже містив правильну build-time configuration: Reverb, SSR і navigation пройшли smoke test без нових browser або Laravel errors.

Сам deployment pipeline я поступово будувала й перевіряла раніше - цей досвід описала в статті про Laravel CI/CD pipeline на реальному проєкті. Цього разу проблема була вже точковою: не як задеплоїти application, а де саме frontend отримує environment configuration.

Що я залишила собі на майбутнє

  • Спочатку визначати, де саме виконується build.

  • Не плутати server runtime .env із Vite build-time environment.

  • Для development і production використовувати окремі environment scopes.

  • Required build variables перевіряти fail-fast до npm run build.

  • Ніколи не передавати server secrets через VITE_*.

  • Після deployment перевіряти browser, services і свіжі Laravel logs, а не тільки green pipeline.

Попередня частина серії

Перед цим я розбирала інший deployment-time edge case - чому лише Laravel 404 page ламала Inertia SSR. Там проблема проявлялася лише в error flow, тут усе визначило місце, де фізично запускався Vite build.

Висновок

Найбільше мене збило те, що production .env був правильним. Я дивилася на правильні values, але не на той етап application lifecycle.

Vite потребував VITE_* variables під час build у GitLab Runner. Коли готовий bundle уже потрапляв на server, змінювати його через server .env було запізно.

Після цього я почала чіткіше розділяти runtime configuration та build-time configuration. А required frontend variables тепер перевіряються pipeline ще до того, як deployment може дійти до server.

Питання було не “чи є variable в .env?”, а “чи була вона там, де запускався npm run build?”.