Skip to content
← Back to the blog
infrastructuresecurity

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.

By Devonauts Team5 min read
Leer en español →
A laptop screen at an angle shows a code debugger with a Threads and Variables panel

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?

ProductionPreviewLocal
Used byYour live domainBranch and pull-request deploymentsYour machine when you run the dev server
Vercel nameProductionPreview (all branches or one branch)Development, downloaded with vercel env pull
Netlify nameProductionDeploy Previews and Branch deploysLocal development (Netlify CLI)
Where it's storedHost dashboardHost dashboardA file like .env.local that is never committed
Who should see real customer dataYesNoNo

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:

VariableProductionPreviewLocal
VITE_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_URLProduction projectStaging projectStaging or a local instance
Supabase anon or publishable keyProduction keyStaging keyStaging key
Supabase service role or secret keyOnly in server functionsStaging, server onlyStaging, server only
Stripe secret keyLive mode (sk_live_…)Test mode (sk_test_…)Test mode
Stripe webhook signing secretLive endpoint's secretTest endpoint's secretFrom the Stripe CLI
Site URL used in emails and redirectshttps://yourapp.comThe preview URL or staging domainhttp://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:

  1. No new deployment. Vercel states that environment variable changes apply only to new deployments. Changing a value and reloading the site does nothing; redeploy.
  2. Wrong environment. You added the variable to Production, but you're testing a preview, or the other way round.
  3. 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.
  4. Invisible typos. A trailing space, quotes pasted into the value, or SUPABASE_URL in the dashboard while the code reads VITE_SUPABASE_URL.

How do I check what a deployment is actually using?

  • On Vercel, the system variable VERCEL_ENV is production, preview or development, 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 ls lists what's configured per environment, and vercel env pull writes your Development variables to a local .env file.
  • For secrets, never print the value. Log whether it is set (Boolean(process.env.STRIPE_SECRET_KEY)) and its prefix if needed, such as sk_test versus sk_live.

What does a clean local setup look like?

  1. Keep a committed .env.example with every variable name and no real values.
  2. Keep your real values in .env.local, which Vite and Next.js projects git-ignore by default in their templates. Check your .gitignore anyway.
  3. Point local at staging or a local Supabase instance, never at production.
  4. If a .env file 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.

Still stuck with your app?

Run the free health check on your app's URL, or get the $299 App Rescue Audit: a senior engineer reviews the code and sends you a written report in 2 business days.