LearnContact
Lesson 5320 min read

Environment Variables

Learn why hardcoding values like API URLs is risky, and how to use .env files with Vite to configure your React app safely per environment.

Introduction

In the last two lessons, every API URL was written directly into the code: https://jsonplaceholder.typicode.com. That works for a tutorial, but real projects usually talk to different servers depending on context — a local development server, a staging server for testing, and a production server for real users. Hardcoding a URL means editing source code every time that changes.

Environment variables solve this by moving configuration values out of your code and into files that can change per environment without touching a single line of a component. This lesson covers how Vite (the build tool most modern React projects use) handles environment variables, and an important security rule you must never break.

What You Will Learn
  • Why hardcoding configuration values in code is a bad practice.
  • How to create a .env file for a Vite-based React project.
  • How to access variables with import.meta.env.
  • How different .env files support different environments.
  • Why anything in a client-side env variable is publicly visible.

Why Not Hardcode Values?

Hardcoding a value like an API base URL directly inside a component seems harmless at first, but it creates real problems as a project grows.

Repeated Everywhere

The same URL string gets copy-pasted into many files, and one typo breaks only some of them.

One Value per Environment

Development, staging, and production usually need different URLs, keys, or feature flags.

Deploy-Time Changes

You should be able to change configuration without editing and rebuilding source code by hand.

Separating Config from Code

Config values are not "logic" — keeping them separate makes code easier to read and reason about.

Environment Variables with Vite

Vite is the build tool that powers most modern React projects created today (via npm create vite@latest). It has built-in support for environment variables through .env files, with one important rule: any variable you want available in your React code must be prefixed with VITE_.

Why the VITE_ Prefix?

Vite only exposes variables prefixed with VITE_ to your client-side code, on purpose. Anything without the prefix stays private to Vite's own build process and is never bundled into the app your users download.

Creating a .env File

Create a file named .env in the root of your project, next to package.json. Each line defines one variable as a simple KEY=value pair.

.env
VITE_API_URL=https://jsonplaceholder.typicode.com
VITE_APP_NAME=PrograMinds Tasks
Restart Required

Vite reads .env files when the dev server starts. If you add or change a variable while the server is already running, you must restart it for the change to take effect.

Accessing Variables in Code

Inside your React code, Vite exposes environment variables through the special import.meta.env object, not through process.env like older Node-based tools.

api/tasks.js
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
});

export function getTasks() {
  return api.get('/todos?_limit=5').then((res) => res.data);
}
console.log(import.meta.env.VITE_API_URL)
https://jsonplaceholder.typicode.com

Now, changing the API URL for a different environment is a one-line edit in a .env file, not a search-and-replace across your entire codebase.

Different .env Files per Environment

Vite automatically supports multiple env files so that development and production can use different values without any extra configuration. This is done purely through file naming.

FileUsed When
.envLoaded in all cases, as a shared default
.env.developmentLoaded automatically when running npm run dev
.env.productionLoaded automatically when running npm run build
.env.localLoaded everywhere but ignored by git — good for personal secrets/overrides

For example, .env.development might point VITE_API_URL at http://localhost:3001, while .env.production points the same variable at your real production API — with zero changes to your component code.

Security Warning: Client-Side Exposure

Critical Security Warning

Any variable prefixed with VITE_ gets bundled directly into the JavaScript files sent to every visitor's browser. Anyone can open browser dev tools, view the source, and read its value. Never put secret API keys, database passwords, or private tokens in a VITE_-prefixed variable — treat them exactly as if they were written in plain text on the page.

True secrets — a private API key that authorizes billing, a database connection string, a signing secret — belong on a backend server, never in client-side React code, regardless of how they are stored. Environment variables in a Vite app are for public configuration values only, like an API base URL or a public analytics ID.

Common Mistakes

Avoid These Mistakes
  • Forgetting the VITE_ prefix, so the variable is silently undefined in your code.
  • Putting a genuinely secret API key in a VITE_ variable, exposing it to every visitor.
  • Not restarting the dev server after adding a new variable to .env.
  • Committing a .env file containing real secrets to a public git repository.
  • Using process.env instead of import.meta.env, which is the Node/Webpack convention, not Vite's.

Best Practices

  • Add .env (and .env.local) to your .gitignore file so secrets never get committed.
  • Commit a .env.example file with placeholder values so teammates know which variables are needed.
  • Only ever put public, non-sensitive values behind the VITE_ prefix.
  • Keep true secrets on a backend server that the client never directly sees.
  • Name variables clearly and consistently, like VITE_API_URL or VITE_APP_NAME.

Frequently Asked Questions

Why does my variable show up as undefined?

The most common cause is a missing VITE_ prefix, or the dev server not being restarted after the .env file was created or changed.

Can I store a secret API key in VITE_SECRET_KEY?

No. Despite the name, Vite still bundles it into the client JavaScript, making it publicly visible to anyone. True secrets must live on a backend server.

What is the difference between .env and .env.local?

.env is meant to be committed with shared, non-sensitive defaults. .env.local is for personal overrides or local secrets and is ignored by git by default.

Do I need a package to use environment variables with Vite?

No, this support is built into Vite itself — no extra installation is needed.

Is import.meta.env the same as process.env?

No. process.env is a Node.js/Webpack convention. Vite uses import.meta.env, a standard JavaScript feature for module-level metadata.

Key Takeaways

  • Hardcoding config values makes projects harder to maintain across environments.
  • Vite requires the VITE_ prefix for a variable to be exposed to client code.
  • Variables are accessed through import.meta.env, not process.env.
  • .env.development and .env.production let the same code use different config automatically.
  • Anything shipped to the client, including VITE_ variables, is publicly visible — never store real secrets there.

Summary

Environment variables let you separate configuration from code, so the same React application can point at different servers or settings depending on where it runs, without editing source files. Vite makes this easy through .env files and the import.meta.env object — but the VITE_ prefix is also a reminder that anything exposed this way is visible to every visitor.

In this lesson, you learned why hardcoding values is risky, how to set up .env files with Vite, how to access variables safely, and the critical rule about never storing secrets client-side. Next, you will start designing your own custom components with well-thought-out prop APIs.

Lesson 53 Completed
  • You understand why hardcoded configuration values are a problem.
  • You can create and use .env files with Vite.
  • You know how per-environment .env files work.
  • You understand what should never be stored client-side.
Next Lesson →

Custom Components