How Environment Names Work — Servers vs React Apps
A concrete comparison: Flow (the ASP.NET Core backend) versus FlowStudio (the React/Vite frontend) — and why one can pick its environment at runtime while the other must commit to one at build time.
Flow: a Named Process, Decided at Startup
Flow is an ASP.NET Core Web API project. Like any .NET process, when it starts it reads
the ASPNETCORE_ENVIRONMENT environment variable (the .NET analogue of Node's
NODE_ENV) from the operating system / container / hosting environment it was launched in.
That single string — typically Development, Staging, or
Production — is available to the process for its entire lifetime, and .NET's configuration
system uses it to decide which layered configuration sources to load.
Because this decision happens when the process starts (not when code is compiled), the exact
same compiled binary can be started as Development on a developer's laptop and as
Production on a server — no rebuild required. Restart the process with a different
ASPNETCORE_ENVIRONMENT value, or change a setting in its configuration store, and the
running behavior changes.
Flow's environment name — and the configuration values that follow from it — are resolved while the process is running, every time it starts. There is no "baking" step.
FlowStudio: a Static Bundle, Decided at Build Time
FlowStudio (BizFirstAiStudio/src/flow-studio/apps/flow-studio) is a React
application built with Vite. It does not run as a long-lived process the way Flow does — it is
compiled ahead of time into a folder of static HTML/CSS/JS files (dist/), which are then
served to a browser. By the time a user's browser loads it, there is no build tool, no Node process,
and no file system running anymore — just static files being interpreted by the browser's JS engine.
That means the "which environment am I" decision cannot happen when the app is used — it has
to happen when the app is built. Vite decides which .env file to read based on
the --mode flag passed to vite build (or vite dev):
# reads .env, .env.local, .env.development, .env.development.local
vite build --mode development
# reads .env, .env.local, .env.production, .env.production.local
vite build --mode production
Whatever values Vite finds in those files at build time get substituted directly into the JavaScript
source as literal strings and bundled into the output files. Once that bundle exists, it is just text
and code sitting in dist/ — there's no mechanism inside it that reads an "environment
name" and picks new values, because the concept of "environment name" doesn't exist in the browser at
all.
A browser tab running the compiled FlowStudio bundle has no idea whether it was built with
--mode development or --mode production. It only ever sees whatever
literal values got baked into the JavaScript at build time. There is no runtime flag it can inspect
to ask "which environment am I."
Side by Side
| Aspect | Flow (ASP.NET Core API) | FlowStudio (React / Vite) |
|---|---|---|
| Environment signal | ASPNETCORE_ENVIRONMENT |
--mode <name> passed to the Vite CLI at build time |
| When it's decided | Process startup (runtime) | Build time, before any browser ever loads the app |
| Can it change without a rebuild? | Yes — restart the process with a new value | No — requires vite build again |
| Where config values live at runtime | Read live via IConfiguration |
Already substituted into static JS text in dist/ |
| Does the running instance "know" its environment name? | Yes — available for the process lifetime | No — only the values that resulted from it survive |
The Diagram
environment-specific config"] end subgraph FlowStudio["FlowStudio — React / Vite"] B1["Developer runs
vite build --mode production"] --> B2["Vite reads .env.production"] B2 --> B3["Values substituted into JS text"] B3 --> B4["Static bundle written to dist/"] B4 --> B5["Browser loads bundle later —
values are already fixed"] end
Flow resolves its environment every time it starts. FlowStudio resolves it once, at build time — long before any browser is involved.