46741 frontend [ Configuration ] Migrate Frontend to Runtime Environment Variables#213
Merged
EBirkenfeld merged 12 commits intoMay 29, 2026
Conversation
Collaborator
|
Describe the test cases clearly, make a table with two columns: |
…DER_ID" This reverts commit 6323286.
pneumatic-ilyacedrik
previously approved these changes
May 22, 2026
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7723849. Configure here.
pneumojoseph
previously approved these changes
May 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

1. Description (Problem)
All frontend feature flags and environment variables (
CAPTCHA,BILLING,AI,SIGNUP,SENTRY_DSN, Firebase config, etc.) were injected at webpack build time viaDefinePlugin+dotenv. This meant:npm run build-client:prodat startup — rebuild on every restart (30–60 sec delay).Where it manifested: frontend container cold-start time, inability to manage features through Railway Dashboard.
2. Context
.env— frontend was the only service with a build-time dependency on env vars.3. Solution
Two-layer env var reading architecture:
getConfig()on the Node.js server readsprocess.envand builds a JSON object__pneumaticConfig, including a newfeatureFlagsblock — passed to the client via a<script>tag in HTML.process.env.Xcalls inenviroment.tsreplaced withgetEnvVar(key), which first checkswindow.__pneumaticConfig.config.featureFlags[key], then falls back toprocess.env[key](for SSR/dev).DefinePluginnow only injectsNODE_ENVandMCS_RUN_ENV— the only variables actually needed at build time.RUNlayer (cached by Docker layer cache). Container start command simplified topm2-runtime start pm2.json.4. Implementation Details
frontend/src/public/constants/enviroment.tsgetEnvVar()function. All 30 variables migrated fromprocess.env.XtogetEnvVar('X'). AddedenvSentryReleaseexport.frontend/src/public/utils/getConfig.tsfeatureFlagsblock (30 keys) to SSR config. UpdatedIBrowserConfiginterface. AddedfeatureFlagstocommon.jsonexposedToBrowser.frontend/src/public/utils/initSentry.tsSENTRY_RELEASEread viaenvSentryReleaseinstead ofprocess.env.SENTRY_RELEASE.frontend/webpack.config.jsdotenvimport and bulkprocess.envinjection viaDefinePlugin. OnlyNODE_ENVandMCS_RUN_ENVremain.frontend/DockerfileRUN NODE_ENV=production npx webpack(build at image build time). AddedCMD ["pm2-runtime", "start", "pm2.json"].docker-compose.yml/docker-compose.src.ymlsh -c "npm run build-client:prod && pm2-runtime start pm2.json"topm2-runtime start pm2.json.frontend/config/common.json"featureFlags"toexposedToBrowserarray.Contract: New field
featureFlags: Record<string, string | undefined>inIBrowserConfig. Non-breaking — all existing fields preserved.5. What to Test
5.1 Unit Tests (
enviroment.test.ts)getEnv— resolution priorityLANGUAGE_CODEset in bothfeatureFlagsandprocess.envfeatureFlags(priority overprocess.env)HOSTmissing from bothfeatureFlagsandprocess.envundefinedSENTRY_DSNset inprocess.envonly,featureFlagsemptyprocess.envvalueBACKEND_URLset inprocess.env,__pneumaticConfigabsentprocess.envvalueWSS_URLset inprocess.env,confighas nofeatureFlagskeyprocess.envvalueBoolean feature flags (
!== 'no'pattern)Each flag follows the same contract:
undefined→true,"no"→false, any other value →true.undefined"no""yes"/ otherCAPTCHA→isEnvCaptchatruefalsetrueGOOGLE_AUTH→isEnvGoogleAuthtruefalseMS_AUTH→isEnvMsAuthtruefalseSSO_AUTH→isEnvSSOAuthtruefalseSIGNUP→isEnvSignuptruefalseBILLING→isEnvBillingtruefalseAI→isEnvAitruefalsePUSH→isEnvPushtruefalseSTORAGE→isEnvStoragetruefalseANALYTICS→isEnvAnalyticstruefalseAdditional:
AI="yes"inprocess.envbut"no"infeatureFlags→isEnvAi=false(featureFlags wins)String variables
SSO_PROVIDER="okta"envSSOProvider="okta"HOST="https://app.pneumatic.app"envHost="https://app.pneumatic.app"ANALYTICS_ID="UA-12345"envAnalyticsId="UA-12345"RECAPTCHA_SITE_KEY="site-key-123"envRecaptchaSiteKey="site-key-123"GOOGLE_CLIENT_ID="google-id-123"envGoogleClientId="google-id-123"SENTRY_RELEASE="v1.2.3"envSentryRelease="v1.2.3"envDevModeNODE_ENV="development"envDevMode=trueNODE_ENV="production"envDevMode=falseenvFirebaseFIREBASE_*vars set inprocess.envenvFirebaseobject populated with all 8 fieldsFIREBASE_*vars set infeatureFlagsenvFirebasereads fromfeatureFlags(priority)5.2 Manual / Integration Tests
Preconditions
Docker build & startup
docker-compose build frontendCompile is done!)docker-compose up frontenddocker-compose build frontendwithout.envfileSSR config injection
window.__pneumaticConfig.config.featureFlags<script>with__pneumaticConfigfeatureFlagsblock is present in HTMLFeature flags — UI Behavior Testing
BILLINGyes/collect-payment-details). Without payment, the user cannot access the product. In the profile menu (avatar → dropdown) the "Pricing" and "Customer Portal" items are visibleno/), without a payment page. In the profile dropdown, "Pricing" and "Customer Portal" items are absentSIGNUPyes/registerroute is accessible and renders the registration form/login→ link at bottom;/forgot-password→ link;/registerdirectlyno/register→ page does not render (404 or redirect)/login;/forgot-password;/registerdirectly in address barGOOGLE_AUTHyes/login;/register; Settings → Team → Invite → popupno/login;/register; Team InvitesMS_AUTHyes/login;/register; Team Invitesno/login;/register; Team InvitesSSO_AUTH+SSO_PROVIDERSSO_AUTH=yes,SSO_PROVIDER=auth0/login;/registerSSO_AUTH=yes,SSO_PROVIDER=okta/login;/registerSSO_AUTH=nono→ the "or" divider is also hidden, only the email/password form is displayed/login;/registerCAPTCHAyesshowCaptcha: true) → the reCAPTCHA widget is displayed. Without completing the captcha, the Submit button is disabled. On public forms — same behavior/register; public form (via forms subdomain)no/register; public formAIyes/templates→ first cardno/templatesPUSHyesnoSTORAGEyesnoANALYTICSyesnoCombination Scenarios (Rollout 3 — if needed)
BILLING=yes+SIGNUP=noGOOGLE_AUTH=yes+MS_AUTH=no+SSO_AUTH=no/login,/registerGOOGLE_AUTH=no+MS_AUTH=no+SSO_AUTH=no/login,/registerBILLING=no+ANALYTICS=no+PUSH=noAI=yes+STORAGE=no/templates+ task with attachmentConnectivity Smoke Test (flag-independent)
BACKEND_URL/api/WSS_URLSENTRY_DSNSENTRY_RELEASESentry.getCurrentHub().getClient().getOptions().releaseLANGUAGE_CODEGeneral Smoke Path (minimum verification)
/login→ verify presence/absence of OAuth buttonswindow.__pneumaticConfig.config.featureFlagsin Console → verify all 30 keysConnectivity
WSS_URLBACKEND_URLSENTRY_DSNSentry.initcalled with correct DSNSENTRY_RELEASEEdge cases
typeof window === 'undefined')getEnvfalls back toprocess.env, no errors__pneumaticConfignot loaded (JS error before hydration)getEnvfalls back safely, app doesn't crashLocal development
npm run dev.envfile available viaprocess.envfallback5.3 What Was NOT Tested
forms.tsx) — behavior unchanged, but smoke-test recommended.6. Affected Areas (Dependencies)
The
enviroment.tsmodule is imported in 20+ files. Key consumers:commonRequest.tsenvBackendURL— requests go to correct URLworkflows/saga.ts,tasks/saga.ts,notifications/saga.tsenvWssURL— connections establishedauth/saga.tsisEnvBillingsettings/reducer.tsenvLanguageCodeMainLayout.tsxisEnvAnalytics,isEnvPushTopNav.tsxisEnvBillingTeamInvitesPopup.tsxisEnvGoogleAuth,isEnvMsAuthUser.tsxisEnvSignupLogin.tsx,Register.tsx,ForgotPassword.tsx,ResetPassword.tsxPublicForm.tsxuploadFiles.tsisEnvStorageinitSentry.tsanalytics.tsMinimum smoke-test: log in → open Dashboard → verify WebSocket → create workflow → open public form.
7. Refactoring
Scale: medium — core configuration module affected.
enviroment.ts— complete mechanism replacement (30 variables), but exported API unchanged (same names, same types). Consumers require no changes.webpack.config.js— removeddotenv, simplifiedDefinePlugin.Dockerfile+docker-compose— moved build from runtime to build-time.Additional testing: Ensure all 20+ consumers of
enviroment.tsreceive correct values. Smoke-test critical paths (see section 6).8. Commits
7a5601a7—46741 feat(frontend): migrate environment variables to runtime config9. Release Notes
Frontend environment variables migrated from build-time webpack injection to SSR runtime config. Feature flags are now served via
window.__pneumaticConfigand can be changed via deployment dashboard without rebuilding the Docker image. Container startup time reduced by eliminating runtime webpack builds.Note
Cursor Bugbot is generating a summary for commit 7723849. Configure here.
Note
[!NOTE]
1. Description (Problem)
All frontend feature flags and environment variables (
CAPTCHA,BILLING,AI,SIGNUP,SENTRY_DSN, Firebase config, etc.) were injected at webpack build time viaDefinePlugin+dotenv. This meant:npm run build-client:prodat startup — rebuild on every restart (30–60 sec delay).Where it manifested: frontend container cold-start time, inability to manage features through Railway Dashboard.
2. Context
.env— frontend was the only service with a build-time dependency on env vars.3. Solution
Two-layer env var reading architecture:
getConfig()on the Node.js server readsprocess.envand builds a JSON object__pneumaticConfig, including a newfeatureFlagsblock — passed to the client via a<script>tag in HTML.process.env.Xcalls inenviroment.tsreplaced withgetEnvVar(key), which first checkswindow.__pneumaticConfig.config.featureFlags[key], then falls back toprocess.env[key](for SSR/dev).DefinePluginnow only injectsNODE_ENVandMCS_RUN_ENV— the only variables actually needed at build time.RUNlayer (cached by Docker layer cache). Container start command simplified topm2-runtime start pm2.json.4. Implementation Details
frontend/src/public/constants/enviroment.tsgetEnvVar()function. All 30 variables migrated fromprocess.env.XtogetEnvVar('X'). AddedenvSentryReleaseexport.frontend/src/public/utils/getConfig.tsfeatureFlagsblock (30 keys) to SSR config. UpdatedIBrowserConfiginterface. AddedfeatureFlagstocommon.jsonexposedToBrowser.frontend/src/public/utils/initSentry.tsSENTRY_RELEASEread viaenvSentryReleaseinstead ofprocess.env.SENTRY_RELEASE.frontend/webpack.config.jsdotenvimport and bulkprocess.envinjection viaDefinePlugin. OnlyNODE_ENVandMCS_RUN_ENVremain.frontend/DockerfileRUN NODE_ENV=production npx webpack(build at image build time). AddedCMD ["pm2-runtime", "start", "pm2.json"].docker-compose.yml/docker-compose.src.ymlsh -c "npm run build-client:prod && pm2-runtime start pm2.json"topm2-runtime start pm2.json.frontend/config/common.json"featureFlags"toexposedToBrowserarray.Contract: New field
featureFlags: Record<string, string | undefined>inIBrowserConfig. Non-breaking — all existing fields preserved.5. What to Test
5.1 Unit Tests (
enviroment.test.ts)getEnv— resolution priorityLANGUAGE_CODEset in bothfeatureFlagsandprocess.envfeatureFlags(priority overprocess.env)HOSTmissing from bothfeatureFlagsandprocess.envundefinedSENTRY_DSNset inprocess.envonly,featureFlagsemptyprocess.envvalueBACKEND_URLset inprocess.env,__pneumaticConfigabsentprocess.envvalueWSS_URLset inprocess.env,confighas nofeatureFlagskeyprocess.envvalueBoolean feature flags (
!== 'no'pattern)Each flag follows the same contract:
undefined→true,"no"→false, any other value →true.undefined"no""yes"/ otherCAPTCHA→isEnvCaptchatruefalsetrueGOOGLE_AUTH→isEnvGoogleAuthtruefalseMS_AUTH→isEnvMsAuthtruefalseSSO_AUTH→isEnvSSOAuthtruefalseSIGNUP→isEnvSignuptruefalseBILLING→isEnvBillingtruefalseAI→isEnvAitruefalsePUSH→isEnvPushtruefalseSTORAGE→isEnvStoragetruefalseANALYTICS→isEnvAnalyticstruefalseAdditional:
AI="yes"inprocess.envbut"no"infeatureFlags→isEnvAi=false(featureFlags wins)String variables
SSO_PROVIDER="okta"envSSOProvider="okta"HOST="https://app.pneumatic.app"envHost="https://app.pneumatic.app"ANALYTICS_ID="UA-12345"envAnalyticsId="UA-12345"RECAPTCHA_SITE_KEY="site-key-123"envRecaptchaSiteKey="site-key-123"GOOGLE_CLIENT_ID="google-id-123"envGoogleClientId="google-id-123"SENTRY_RELEASE="v1.2.3"envSentryRelease="v1.2.3"envDevModeNODE_ENV="development"envDevMode=trueNODE_ENV="production"envDevMode=falseenvFirebaseFIREBASE_*vars set inprocess.envenvFirebaseobject populated with all 8 fieldsFIREBASE_*vars set infeatureFlagsenvFirebasereads fromfeatureFlags(priority)5.2 Manual / Integration Tests
Preconditions
Docker build & startup
docker-compose build frontendCompile is done!)docker-compose up frontenddocker-compose build frontendwithout.envfileSSR config injection
window.__pneumaticConfig.config.featureFlags<script>with__pneumaticConfigfeatureFlagsblock is present in HTMLFeature flags in UI
BILLING="no"CAPTCHA="no"AI="yes"SIGNUP="no"BILLINGfrom env entirelyisEnvBilling=true(default-on behavior preserved)Connectivity
WSS_URLBACKEND_URLSENTRY_DSNSentry.initcalled with correct DSNSENTRY_RELEASEEdge cases
typeof window === 'undefined')getEnvfalls back toprocess.env, no errors__pneumaticConfignot loaded (JS error before hydration)getEnvfalls back safely, app doesn't crashLocal development
npm run dev.envfile available viaprocess.envfallback5.3 What Was NOT Tested
forms.tsx) — behavior unchanged, but smoke-test recommended.6. Affected Areas (Dependencies)
The
enviroment.tsmodule is imported in 20+ files. Key consumers:commonRequest.tsenvBackendURL— requests go to correct URLworkflows/saga.ts,tasks/saga.ts,notifications/saga.tsenvWssURL— connections establishedauth/saga.tsisEnvBillingsettings/reducer.tsenvLanguageCodeMainLayout.tsxisEnvAnalytics,isEnvPushTopNav.tsxisEnvBillingTeamInvitesPopup.tsxisEnvGoogleAuth,isEnvMsAuthUser.tsxisEnvSignupLogin.tsx,Register.tsx,ForgotPassword.tsx,ResetPassword.tsxPublicForm.tsxuploadFiles.tsisEnvStorageinitSentry.tsanalytics.tsMinimum smoke-test: log in → open Dashboard → verify WebSocket → create workflow → open public form.
7. Refactoring
Scale: medium — core configuration module affected.
enviroment.ts— complete mechanism replacement (30 variables), but exported API unchanged (same names, same types). Consumers require no changes.webpack.config.js— removeddotenv, simplifiedDefinePlugin.Dockerfile+docker-compose— moved build from runtime to build-time.Additional testing: Ensure all 20+ consumers of
enviroment.tsreceive correct values. Smoke-test critical paths (see section 6).8. Commits
7a5601a7—46741 feat(frontend): migrate environment variables to runtime config9. Release Notes
Frontend environment variables migrated from build-time webpack injection to SSR runtime config. Feature flags are now served via
window.__pneumaticConfigand can be changed via deployment dashboard without rebuilding the Docker image. Container startup time reduced by eliminating runtime webpack builds.Changes since #213 opened
npm run build-client:prodscript instead of directly invokingnpx webpackwithNODE_ENV=production[7723849]NODE_OPTIONSenvironment variable to--max-old-space-size=3072in the Dockerfile [3cfe1dc]