Skip to content

React Tutorial Modern React and Production

Environment-Specific Configuration in React and Vite

Lesson 133 of 156 3 min read By Deepak Paswan Updated Oct 10, 2026

Full-project lesson · needs Node.js, npm and real project files, so it does not run in the in-page playground.

A real project runs in several environments: your computer, a test or “staging” site, and production. Each needs different settings. This lesson shows how to organise them safely with Vite. It builds on Environment Variables.

Which file wins?

Vite loads .env first and then lets more specific files override it. When you run npm run build, the mode is production, so .env.production overrides .env.

Create a shared .env with a development value:

.env
PLAIN
VITE_API_URL=http://localhost:3000

and a .env.production with the real value:

.env.production
PLAIN
VITE_API_URL=https://api.example.com

The app prints which one it received:

src/App.jsx
JSX
function App() {
  return <p>API: {import.meta.env.VITE_API_URL}</p>
}

export default App

We built this project with vite build and checked two things: the page shows https://api.example.com, and the string http://localhost:3000 does not appear anywhere in the built files. The development value never reaches production, which is exactly what you want.

Modes

A mode is a name that selects which .env.[mode] files load. The defaults are development for vite and production for vite build. You can create more:

terminal
BASH
npx vite build --mode staging    # loads .env and .env.staging

Typical setup:

FileUsed for
.envDefaults shared by all modes
.env.developmentnpm run dev
.env.stagingA test site that mirrors production
.env.productionnpm run build
.env.local, .env.*.localPersonal overrides; ignored by Git

Keep the variable names the same in every file and change only the values. Document them in .env.example.

Build-time vs runtime configuration

Vite inlines VITE_ values at build time. That has a consequence: one build equals one environment. To deploy the same app to staging and production, you build twice, each with its own values.

If you want a single build that you can promote from staging to production, load configuration when the app starts, from a file served next to it:

public/config.json
PLAIN
{ "apiUrl": "https://api.example.com" }
App.jsx
JSX
// Load it once, before rendering the app
const config = await fetch('/config.json').then((r) => r.json())
createRoot(document.getElementById('root')).render(<App config={config} />)

The deployment process then replaces config.json per environment. Because it is a public file, it can hold only public settings, never secrets. Make sure config.json is not cached forever (give it a short cache time).

What belongs in configuration

GoodBad
The API base URLA private API key or token
A public analytics or site idA database password
A feature flag (VITE_SHOW_BETA=true)An admin credential
A maps key restricted to your domainA payment secret key
The app’s public URLAnything you would not publish on your site

Everything that reaches the browser is public; see Why Secret Keys Must Not Be Exposed.

Feature flags

A simple flag lets you ship code that is not enabled yet:

App.jsx
JSX
const showBeta = import.meta.env.VITE_SHOW_BETA === 'true'   // env values are strings

{showBeta && <BetaBanner />}

Remember that import.meta.env.VITE_X is always a string: compare with 'true', not true. For per-user flags, use a server or a feature-flag service.

Validate configuration early

A missing variable should fail clearly, not as a mysterious “undefined” bug later:

script.js
JS
export function requireEnv(name) {
  const value = import.meta.env[name]
  if (!value) throw new Error(`Missing environment variable ${name}`)
  return value
}

export const API_URL = requireEnv('VITE_API_URL')

Different base paths and URLs

Also configure the public path and the URLs your app uses per environment: the base option, the router basename, the API URL and the canonical URL for SEO. A staging site often lives at a different domain or in a subfolder (Deployment Fundamentals).

Common mistakes

  • Putting production secrets into .env and committing them.
  • Expecting to change VITE_ values after the build.
  • Different variable names per environment, so the code needs conditionals.
  • Forgetting to restart the dev server after editing .env.
  • Reading booleans and numbers from env without converting them from strings.

Key points

  • .env.production overrides .env in npm run build; the development value never reaches the production bundle.
  • VITE_ values are inlined at build time, so one build serves one environment. Use a runtime config.json for build-once, deploy-many.
  • Configuration is public: no secrets, early validation, and consistent names.