Під час production deployment Laravel-застосунку я отримала дивну ситуацію: усі VITE_REVERB_* variables були в server .env, але frontend усе одно падав із помилкою Pusher.
Спочатку я очікувала, що достатньо перевірити .env на сервері. Значення там справді були.
Проблема виявилася в іншому місці: Vite bundle збирався не на production server, а раніше - всередині GitLab CI Runner.
Це четверта частина серії. У попередній статті я розбирала 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 на місці.
Вони були на місці.
You must pass your app key when you instantiate Pusher.Server .env виглядав правильно
Я окремо перевірила на сервері, що всі потрібні Reverb та Vite variables справді задані, не виводячи їхні значення в log.
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Ключова деталь: Vite потрібні values під час build
Frontend читав Reverb settings через import.meta.env.
Це означало, що потрібні VITE_* values мали бути доступні саме тому process, який виконував npm run build. У моєму pipeline цей process працював усередині GitLab Runner.
Production .env на сервері з’являвся в зовсім іншому середовищі й уже не міг змінити готовий JavaScript bundle.
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.
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 |
|
VITE_REVERB_HOST | Vite frontend build |
|
VITE_REVERB_PORT | Vite frontend build |
|
VITE_REVERB_PORT | Vite frontend build |
|
REVERB_APP_SECRET | Laravel / Reverb server | server only |
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
Я додала fail-fast перевірки
Після цього я не хотіла залишати ситуацію, де variable знову випадково видалять, а pipeline все одно збере broken bundle.
Тому перед build я додала прості shell checks. Якщо хоча б одного required value немає, deployment падає одразу.
Для мене це набагато краще, ніж успішний pipeline із JavaScript bundle, який ламається вже в browser.
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.
Перед 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.
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.
php artisan inertia:check-ssrInertia SSR server is running.sudo supervisorctl status | grep '^icanup'Після production smoke test у browser не було жодної console error, а після контрольної log marker не з’явилося жодного нового Laravel error.
Це була не перша зміна мого GitLab pipeline
Сам 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?”.



