An environment variable is a named value that your app reads at runtime from its environment rather than from its source code. Secrets like API keys and database URLs are kept in environment variables, usually loaded from a gitignored .env file locally and set in your host's dashboard in production, so the real values never live in your code or git history.
Why not just put secrets in code
A secret written into source is in every copy of the repo forever, including its history, and if the repo is ever public it is scraped within minutes. Reading the secret from an environment variable means the code references a name, process.env.STRIPE_KEY, while the value stays outside the codebase.
The .env and .env.example pattern
Keep real values in a .env that is listed in .gitignore, so it is never committed. Commit a .env.example with the variable names and empty values, so anyone (or any AI tool) can see the shape of the config without seeing the secrets. Confirm .env is gitignored before your first commit.
Next.js: NEXT_PUBLIC_ prefix controls browser exposure
In Next.js, any environment variable prefixed with NEXT_PUBLIC_ is bundled into the client-side JavaScript and sent to every browser that loads the page. Variables without that prefix are server-only. This means NEXT_PUBLIC_SUPABASE_URL is safe to expose (it's your public project URL), but SUPABASE_SERVICE_ROLE_KEY should never have the prefix — it would reach every user's browser. A common AI-generated mistake is adding NEXT_PUBLIC_ to every env var for convenience, which exposes server secrets client-side.
Vite: import.meta.env and the VITE_ prefix
Vite uses import.meta.env instead of process.env, and variables must be prefixed with VITE_ to be included in the browser bundle. Any variable without that prefix is not available in client code. The equivalent of Next.js's NEXT_PUBLIC_ pattern is VITE_: safe for public values, not for secrets. Server-only Vite variables exist only in SSR mode; a static Vite site has no server context.
Setting environment variables in production
In local development, a .env file loaded by your framework provides the values. In production, you set them directly in your host's dashboard — Vercel under Project Settings > Environment Variables, Railway under Variables, Render under Environment. The .env file is never uploaded to the host; the values are set separately per deployment environment. This is intentional: it lets you use different values for staging and production without changing any code.
Safe vs unsafe ways to handle .env
| Practice | Result |
|---|---|
| .env listed in .gitignore | Secrets stay out of git (correct) |
| .env committed to the repo | Secrets exposed in history, rotate them |
| Real values placed in .env.example | Secrets leak through the committed example |
| Server-only var, no public prefix | Value stays on the server |
| NEXT_PUBLIC_ on a server secret | Secret bundled into browser JS, exposed to all users |
| Vars set in host dashboard (Vercel/Railway) | Correct — values per environment, never in code |
What this means for AI-generated code
AI tools often create a .env with real values but forget the matching .gitignore entry, so the secrets get committed on the first push. They also inline keys directly when an integration needs one, because that is the shortest path to running code.
Common questions
How do I keep my .env file out of git?
Add a line with .env to your .gitignore before your first commit, and confirm git is not already tracking it by running git status. If it was committed earlier, removing it now is not enough: it stays in history, so rotate the secrets too.
What is the difference between .env and .env.example?
.env holds the real secret values and is gitignored, so it never leaves your machine or host. .env.example is committed and lists only the variable names with empty or placeholder values, so collaborators and AI tools can see what config is needed without seeing the secrets.
I already committed my .env file. What should I do?
Rotate every secret that was in it, because it now lives in your git history and may have been scraped. Then add .env to .gitignore and remove it from tracking. Deleting the file in a later commit does not remove it from history.
Is an environment variable enough to keep a secret private?
It keeps the secret out of your source and git history, but not out of the browser. If a client-side component reads the variable, the value is bundled and shipped to every visitor. A secret is only private if it is read and used in server code.
What does NEXT_PUBLIC_ do in Next.js?
Any environment variable prefixed NEXT_PUBLIC_ is included in the browser bundle and sent to every visitor. Use it for values that are intentionally public — your Supabase project URL, your Stripe publishable key. Never prefix a secret (service_role key, Stripe secret key, database connection string) with NEXT_PUBLIC_; it will be readable in the browser's Network tab by anyone.
How do I set environment variables in Vercel?
In the Vercel dashboard, go to your project, then Settings > Environment Variables. Add each variable with a name and value, and select which environments it applies to (Production, Preview, Development). Variables added here are injected at build and runtime; you never upload a .env file to Vercel.
What is the difference between process.env and import.meta.env?
process.env is the Node.js way to read environment variables, used by Next.js on the server. import.meta.env is Vite's way. In a Vite project (including SvelteKit and some React setups), you use import.meta.env.VITE_MY_VAR. In Next.js, you use process.env.MY_VAR on the server or process.env.NEXT_PUBLIC_MY_VAR in client code. They both read the same underlying environment variables; the syntax and prefix rules differ by framework.
Can AI tools accidentally expose my secrets?
Yes, in two common ways. First, AI tools often create a .env with real values but forget to add .env to .gitignore, so the first commit includes the secrets. Second, they add NEXT_PUBLIC_ to every environment variable for convenience, which exposes server-only secrets to the browser. Always review the .gitignore and env variable prefixes when accepting AI-generated code.