What you'll learn
  • Say what an environment variable is and why the value lives outside your code
  • Keep .env out of git and know what to do the day a secret gets committed anyway
  • Commit a .env.example so teammates know which variables the app needs
  • Predict which variables ship to the browser based on prefixes like PUBLIC_ and VITE_
  • Validate required variables at boot instead of failing mid-request later
on this page

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

MistakeWhat happensFix
Commit .env to GitSecrets exposed publiclyAdd to .gitignore, rotate keys
Use NEXT_PUBLIC_ for a secretSecret visible in browser bundleRemove prefix, use server-side only
No .env.exampleTeammates don’t know what vars existAdd .env.example with fake values
No validation on startupApp silently fails laterValidate required vars at boot
Different var names in prodApp works locally, breaks in prodMatch 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.

The rest of the Dev Setup series

Environment variables are one piece of turning your machine and your projects into something production-ready. The rest of the series:

signal check

Can you answer these without scrolling back up?

  1. You committed a .env file with real secrets. Is deleting it enough?

    No. If a .env file with real secrets was ever committed, those secrets are compromised. Rotate them immediately and make sure .env is in .gitignore so it cannot happen again.

  2. Is it safe to put an API secret in NEXT_PUBLIC_API_KEY?

    No. Any variable with a public prefix like PUBLIC_, VITE_, or NEXT_PUBLIC_ ends up visible in your compiled JavaScript bundle, where anyone who views source can read it. Secrets belong in unprefixed server-only variables.

  3. How do env vars get set in production if .env is never deployed?

    You set them in the hosting provider's dashboard, like Vercel or Netlify under Environment Variables. They get injected into your build and runtime process securely, and you never commit them.

progress is stored in this browser only