Environment Variables & API URLs
Part of the Full Stack Developer Roadmap
In short
Your app works on your laptop, then you deploy and it's still calling localhost:4000. The fix is simple: read the API URL from an environment variable instead of typing it into code.
Configuring your frontend to call the right backend URL across local, staging and production — without hardcoding `localhost` into something that ships to real users.

Think of it like this
Same phone, different SIM
When you travel abroad, you don't buy a new phone. You swap the SIM, and the same phone now calls through a different network. Your frontend code is the phone and the env file is the SIM. Local, staging and production each slot in their own API URL.
Watch it happen
How VITE_API_URL reaches the browser
1/5.env.production says VITE_API_URL=https://api.myshop.in. Only variables starting with VITE_ are exposed to client code.
The big ideas
What you need to know
Public prefixes
Vite exposes only VITE_ variables to the browser, Next.js only NEXT_PUBLIC_ ones. Everything else stays on the server.
Frontend env vars are not secret
Whatever reaches the bundle is visible to everyone. Never put a database password or a Stripe secret key there.
Baked in at build time
Client env values are replaced during the build. Changing one in your host's dashboard needs a fresh build.
One file per environment
.env.development for local, .env.production for the live build. Commit a .env.example and keep real secrets out of Git.
See it in code
Walk through the code
1// src/config.ts2const apiUrl = import.meta.env.VITE_API_URL;3if (!apiUrl) {4 throw new Error("VITE_API_URL is missing. Check your .env file.");5}6export const API_URL = apiUrl.replace(/\/$/, "");7 8// src/api.ts9import { API_URL } from "./config";10 11export async function api<T>(path: string, init?: RequestInit): Promise<T> {12 const res = await fetch(`${API_URL}${path}`, { credentials: "include", ...init });13 if (!res.ok) throw new Error(`${init?.method ?? "GET"} ${path} failed: ${res.status}`);14 return res.json() as Promise<T>;15}1/3Fail loudly the moment the app starts if the variable is missing, instead of calling 'undefined/products'.
Put VITE_API_URL=http://localhost:4000 in .env.development and VITE_API_URL=https://api.myshop.in in .env.production. Vite picks the file from the mode.
Save yourself the pain
Common mistakes
Oops
Putting STRIPE_SECRET_KEY in a VITE_ variable so the frontend can use it.
Do this instead
Secrets belong only in backend env vars. The frontend calls your API, and the API uses the secret.
Oops
Changing the env var on Vercel and expecting the live site to update instantly.
Do this instead
Client values are baked in at build time. Trigger a redeploy after changing them.
Your mission
Two environments, one codebase
Quick check
You put VITE_DB_PASSWORD in your React app's .env file and use it in code. What happens after you build?
Go deeper
- 1Use an environment variable for your API base URL instead of hardcoding it.
- 2Set different values for local development vs your deployed production API.
- 3Learn which env vars are safe to expose to the frontend bundle and which must stay backend-only.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.