Stop 09 · Connecting Frontend & BackendTopics · 4/8

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.

The blob character swaps a SIM card into a phone, with a second SIM and a suitcase on the table.

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

.env file
vite build
JS bundle
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

ts
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

~20 min

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

  1. 1Use an environment variable for your API base URL instead of hardcoding it.
  2. 2Set different values for local development vs your deployed production API.
  3. 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.

Download
InsideEdge