Environment variables: production vs preview vs local
How production, preview and local environment variables differ on Vercel and Netlify, which values go in each, and the mistakes that leak keys or break deploys.
Leer en español →
Production, preview and local environment variables are three separate lists: production is what your live site uses, preview is what branch and pull-request builds use, and local lives in a file on your own machine. A variable set in one list does not exist in the others, a change only takes effect on the next deployment, and anything with a public prefix such as VITE_ or NEXT_PUBLIC_ is baked into the browser code at build time.
This guide explains how the three environments work on Vercel and Netlify, what values belong in each one for a typical Supabase and Stripe app, and how to check what a deployment is really using.
How do the three environments differ?
| Production | Preview | Local | |
|---|---|---|---|
| Used by | Your live domain | Branch and pull-request deployments | Your machine when you run the dev server |
| Vercel name | Production | Preview (all branches or one branch) | Development, downloaded with vercel env pull |
| Netlify name | Production | Deploy Previews and Branch deploys | Local development (Netlify CLI) |
| Where it's stored | Host dashboard | Host dashboard | A file like .env.local that is never committed |
| Who should see real customer data | Yes | No | No |
Vercel also supports custom environments, and both platforms let you override a preview value for one specific branch. On Vercel, a branch-specific value replaces the general preview value with the same name, so you only add the ones that differ.
Which values go where?
For a common setup (front end on Vercel or Netlify, Supabase for data and login, Stripe for payments), a sane split looks like this:
| Variable | Production | Preview | Local |
|---|---|---|---|
VITE_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_URL | Production project | Staging project | Staging or a local instance |
| Supabase anon or publishable key | Production key | Staging key | Staging key |
| Supabase service role or secret key | Only in server functions | Staging, server only | Staging, server only |
| Stripe secret key | Live mode (sk_live_…) | Test mode (sk_test_…) | Test mode |
| Stripe webhook signing secret | Live endpoint's secret | Test endpoint's secret | From the Stripe CLI |
| Site URL used in emails and redirects | https://yourapp.com | The preview URL or staging domain | http://localhost:5173 or similar |
The key idea: preview builds should never touch production data or charge real cards. If you don't have a staging database yet, our guide to a simple staging setup shows the cheapest way to add one.
Which variables are visible in the browser?
Front-end frameworks only expose variables with a public prefix. In Vite that is VITE_; in Next.js it is NEXT_PUBLIC_. These are replaced with their literal values when the app is built, so anyone can read them in the browser's developer tools. The Vite documentation is explicit that VITE_ variables should not contain sensitive information such as API keys.
That gives you a simple rule:
- Safe to be public: the Supabase URL, the Supabase anon or publishable key (as long as Row Level Security protects your tables), Stripe's publishable key, analytics IDs.
- Never public: the Supabase service role or secret key, Stripe secret keys, OpenAI or other AI API keys, webhook signing secrets, database passwords.
If an AI tool wrote VITE_OPENAI_API_KEY or NEXT_PUBLIC_STRIPE_SECRET_KEY, that key is already exposed in your live site. Move the call to a server function and rotate the key at the provider. Vercel lets you mark variables as Secret, which makes them write-only after saving; that protects the value in the dashboard but does not stop your code from exposing it with a public prefix.
Why didn't my change take effect?
Four reasons account for most of the confusion:
- No new deployment. Vercel states that environment variable changes apply only to new deployments. Changing a value and reloading the site does nothing; redeploy.
- Wrong environment. You added the variable to Production, but you're testing a preview, or the other way round.
- Build-time baking. Public variables are frozen into the bundle at build time. Even server-rendered apps read them from the build, not from the dashboard at request time.
- Invisible typos. A trailing space, quotes pasted into the value, or
SUPABASE_URLin the dashboard while the code readsVITE_SUPABASE_URL.
How do I check what a deployment is actually using?
- On Vercel, the system variable
VERCEL_ENVisproduction,previewordevelopment, so your code can tell where it's running. You can show it in a footer on non-production builds or log it in a server function. - In the browser, open the Network tab and look at which Supabase project URL the requests go to. That tells you immediately whether a preview is talking to production.
- From the CLI,
vercel env lslists what's configured per environment, andvercel env pullwrites your Development variables to a local.envfile. - For secrets, never print the value. Log whether it is set (
Boolean(process.env.STRIPE_SECRET_KEY)) and its prefix if needed, such assk_testversussk_live.
What does a clean local setup look like?
- Keep a committed
.env.examplewith every variable name and no real values. - Keep your real values in
.env.local, which Vite and Next.js projects git-ignore by default in their templates. Check your.gitignoreanyway. - Point local at staging or a local Supabase instance, never at production.
- If a
.envfile with real keys was ever pushed to GitHub, deleting it is not enough. The keys are in the history, so rotate them.
Quick answers
Do I need to redeploy after changing a variable? Yes. Existing deployments keep the values they were built with.
Can a preview use production variables? Technically yes, but it means test branches write to real data and can charge real cards. Give previews their own values.
Is the Supabase anon key a secret? No, it's meant to be public, but only Row Level Security makes that safe. Our guide on whether the Supabase anon key is safe to expose explains the conditions.
How big can environment variables be on Vercel? The total for a deployment is 64 KB across all variables, with a smaller per-variable limit for edge functions.
Want your environments set up properly?
Separating production from everything else is one of the cheapest ways to prevent a bad day. Our maintenance and support plans include setting up environments and rotating leaked keys, and our security checklist for AI-built apps covers what to check next. You can also run the free health check to see if anything sensitive is visible from outside.


