You’ve seen them everywhere: process.env.API_KEY, import.meta.env.PUBLIC_URL, .env files. But nobody explains what they actually are, why they exist, or how to not accidentally push your secrets to GitHub.
This guide covers all of it.
What is an environment variable?
An environment variable is a value stored outside your code that your code can read at runtime. Instead of this:
// ❌ Hardcoded — dangerous and inflexible
const apiKey = "sk_live_abc123xyz789supersecret";
You write this:
// ✅ Read from environment
const apiKey = process.env.STRIPE_API_KEY;
The actual value lives somewhere else — a .env file, your CI/CD system, or your hosting provider’s dashboard.
Why this matters
Three big reasons:
1. Security. Secrets hardcoded in source code get committed to Git and shared with anyone who has access to the repo. Environment variables stay out of Git.
2. Different values per environment. Your dev database URL is different from your production database URL. Environment variables let the same code behave differently in development, staging, and production.
3. Easy to rotate. If an API key is compromised, you update one value in your hosting provider’s dashboard — not a hundred files in your codebase.
The .env file
Most frameworks use a .env file for local development:
# .env
DATABASE_URL=postgres://localhost:5432/myapp
STRIPE_SECRET_KEY=sk_test_abc123
PUBLIC_API_BASE=https://api.example.com
Your framework (Vite, Next.js, Astro, etc.) reads this file automatically when you run npm run dev.
The .env.example file
Since .env is gitignored, new team members won’t know what variables are needed. The solution: commit a .env.example with fake placeholder values.
# .env.example — safe to commit, no real values
DATABASE_URL=postgres://localhost:5432/myapp_dev
STRIPE_SECRET_KEY=sk_test_your_key_here
PUBLIC_API_BASE=https://api.example.com
This is the team’s documentation for “what env vars does this app need?”
When someone clones the repo:
cp .env.example .env
# Then fill in the real values
Public vs private variables
Most frameworks distinguish between public (exposed to the browser) and private (server-only) variables.
In Astro:
# Available in browser JS — prefix PUBLIC_
PUBLIC_SITE_URL=https://raindev.fyi
# Server-only — never sent to browser
DATABASE_URL=postgres://...
STRIPE_SECRET_KEY=sk_live_...
In Vite/Vue/React:
# Available in browser — prefix VITE_
VITE_API_URL=https://api.example.com
# Not available in browser (no prefix)
SECRET_KEY=supersecret
In Next.js:
# Available in browser — prefix NEXT_PUBLIC_
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
# Server-only
DATABASE_URL=postgres://...
Setting env vars in production
In production, you don’t use a .env file. Instead, you set variables in your hosting provider’s dashboard:
- Vercel: Project Settings → Environment Variables
- Netlify: Site Settings → Environment Variables
- Railway / Render / Fly.io: their respective dashboards
These are injected into your build and runtime process securely. You never commit them.
Reading env vars in your code
Node.js / Astro (server-side):
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) throw new Error("DATABASE_URL is not set");
Astro (client or server):
const siteUrl = import.meta.env.PUBLIC_SITE_URL;
Validating at startup:
For critical variables, validate them when your app boots rather than failing at runtime when a request comes in:
// src/lib/env.ts
const requiredVars = ["DATABASE_URL", "STRIPE_SECRET_KEY"] as const;
for (const key of requiredVars) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
export const env = {
databaseUrl: process.env.DATABASE_URL!,
stripeKey: process.env.STRIPE_SECRET_KEY!,
};
The most common mistakes
| Mistake | What happens | Fix |
|---|---|---|
Commit .env to Git | Secrets exposed publicly | Add to .gitignore, rotate keys |
Use NEXT_PUBLIC_ for a secret | Secret visible in browser bundle | Remove prefix, use server-side only |
No .env.example | Teammates don’t know what vars exist | Add .env.example with fake values |
| No validation on startup | App silently fails later | Validate required vars at boot |
| Different var names in prod | App works locally, breaks in prod | Match names exactly across environments |
Environment variables are one of those things that seem annoying until the day you accidentally leak a database password. Set them up right from the start.