Each part’s full env table is in its own doc — this page is the map and the gotchas.
Conventions
- Each part ships two templates,
env/env.dev and env/env.prod; a script copies the chosen
one to .env: npm run switch_to_dev / switch_to_prod. .env is gitignored.
VITE_* (app, admin) and PUBLIC_* (website) are inlined into the built bundle — so
switch to prod before building, and never put a secret in one (it ships to every
visitor). They’re for public values: URLs, public keys, feature flags.
- Real secrets live only in the API’s server environment (
env.prod on the server) — filled
into the CHANGE-ME placeholders, never committed.
Where each part’s variables live
The minimum to boot the backend
DATABASE_URL, REDIS_URL, JWT_SECRET (≥32 chars). Everything else (Google/Apple, SMTP,
RevenueCat, OneSignal, admin) is optional and inert until configured — an unconfigured
integration returns a clear error rather than crashing at boot.
Cross-part wiring that must agree
- App/website → API base:
VITE_BACKEND_URL (app), PUBLIC_SUPPORT_ENDPOINT (website
support form). Empty backend URL on the website = support form runs in demo mode.
- Admin origin ↔ API CORS: set
ADMIN_URL in the API env to the admin’s origin, or CORS
refuses it (looks like an auth bug). Native schemes are always allowed.
CORS_ORIGINS (API) must list each web origin (app web build, website) exactly.
client_id: app VITE_CLIENT_ID (default app), admin admin — just a session label.
Gotchas
OneSignal: the app only knows VITE_ONESIGNAL_APP_ID (public). The REST API key is a
server secret in shipwide_api — never move it into a VITE_*.
VITE_APP_NAME namespaces all local data on the app (encrypted storage, settings, cache,
deep-link scheme). Changing it after launch orphans users’ existing local data.
- Add any new
VITE_* to src/vite-env.d.ts — vite/client’s index signature means a typo
is not a type error, it just reads undefined.