Vercel environment variables are scoped to one of four environments (Production, Preview, Development, and the implicit all for everywhere), can be marked sensitive (encrypted at rest, decrypted only inside the build and runtime) or regular, and are decrypted from Vercel’s secret store at build time and runtime. The most common “my env var is missing” report is a variable that is set in Production but not in Preview, or set in Preview but not in Development. The reason “vercel env variables” is still a top search is that the four-environment model is more powerful than the docs make it sound, and the protection level has implications the docs do not flag.
This post is the version I would want to read before setting the first env var on a Vercel project, with the four-environment model, the sensitive-vs-regular choice, and the secrets pattern that does not involve committing anything.
Table of contents
- The direct answer: four environments, two protection levels
- The four environments and when each one is used
- The sensitive vs regular choice
- The decryption story: how Vercel handles the secret
- The build-time vs runtime split
- The Next.js-specific gotchas (
NEXT_PUBLIC_and$env) - The secrets pattern that scales
- FAQ
The direct answer: four environments, two protection levels
Vercel’s env variable model is a 4 × 2 matrix:
- Environments: Production, Preview, Development, and the implicit “all environments” (the default for new variables).
- Protection levels: Sensitive (encrypted at rest, only decrypted in the build and runtime) and Regular (visible to anyone with read access to the project).
The four environments map to the four deploy contexts:
- Production is the live site, deployed from the main branch (or whichever branch the project’s Production Branch setting points to).
- Preview is every other branch’s deploy, including PR previews.
- Development is the local dev server (
vercel dev). - All (the default) means the variable is available in all three.
A new variable that is added to “All environments” is available to Production, Preview, and Development. A new variable added to “Production only” is missing from Preview and Development, and the build fails when the Preview deploy tries to read it.
The four environments and when each one is used
The environment list, with the right mental model for each:
Production. The live site. The variable is decrypted and made available to the build that runs on every push to the production branch, and to the runtime that serves production traffic. The right place for production secrets: the production database connection string, the production API keys, the production OAuth client secret.
Preview. Every other branch and every PR. The variable is decrypted and made available to the build and runtime for the preview deploy. The right place for preview secrets: a staging database connection string, a sandbox API key, a test-mode OAuth client. Most “my preview deploy is failing” reports turn out to be a variable that is set in Production but not in Preview. Set the variable in Preview (and the value will be different from Production) before opening the PR.
Development. The local dev server, started with vercel dev. The variable is decrypted from the project’s env store and made available to the dev server. The right place for local secrets: a local database URL, a development OAuth client, a local-only feature flag. The values should be different from Production.
All (the default). The variable is available in all three environments. The right place for non-secret configuration: a feature flag, a public API URL, a value the app needs in every environment.
The decision tree:
- Is it a secret? → Sensitive, and set per-environment.
- Is it a non-secret configuration that is the same in every env? → Regular, set in “All.”
- Is it a non-secret configuration that is different per env? → Regular, set per-environment.
The sensitive vs regular choice
The choice between Sensitive and Regular is not about who can read the variable in the Vercel dashboard (every team member with project access can read every variable in the dashboard either way). The choice is about how the variable is stored and decrypted.
Regular — the value is stored in Vercel’s database in plain text. It is visible in the project’s env variable list to anyone with project access. It is decrypted at build time and made available to the build and runtime as a normal environment variable. The right choice for non-secrets.
Sensitive — the value is encrypted at rest using a per-project encryption key. The plaintext is never shown in the Vercel dashboard; you can only set, rotate, or delete it. The value is decrypted at build time and made available to the build and runtime. The right choice for any secret.
The interesting detail: a Sensitive variable is still decrypted and made available as a normal environment variable to the build and runtime. The build process sees the plaintext, the runtime sees the plaintext, and any code that runs in the build or runtime can read the variable. The protection is at-rest encryption, not runtime isolation.
The right way to think about it: Sensitive prevents the secret from being readable by someone with dashboard access who should not have the secret. It does not prevent the secret from being read by a build step that echoes the env, or by a runtime that logs the env in an error path. The Sensitive flag is necessary, not sufficient. The “do not echo secrets in logs” discipline is the rest.
The decryption story: how Vercel handles the secret
When Vercel runs a build:
- The build container is created with the env variables for the build’s environment (Production, Preview, or Development).
- Sensitive variables are decrypted from Vercel’s secret store using the project’s encryption key.
- All variables (regular and sensitive) are exported into the build container’s environment.
- The build runs (
next build, the framework’s build command). - The build output is uploaded to Vercel’s edge.
- At runtime, the variables are decrypted again and made available to the runtime.
The interesting detail: the build container has the decrypted values. A build step that runs env will print every variable, including the sensitive ones. The same applies to a build step that runs a script which logs the env. Sensitive is at-rest protection, not build-time protection.
For a build that should not see a production secret, the answer is to set the secret only in the environments that need it. A sensitive variable scoped to Production is not available to Preview builds, which is the right behavior for a secret that should not be reachable from a feature branch.
The build-time vs runtime split
Vercel has two distinct contexts for env variables: the build context and the runtime context.
Build-time is the next build (or equivalent) process. The env variables available here are the ones for the build’s environment. The build produces a static output (for static pages) and a serverless function bundle (for server-side pages). Only variables prefixed with NEXT_PUBLIC_ (in Next.js) are inlined into the client bundle. The rest are server-only.
Runtime is the serverless function execution. The env variables available here are the same as the build-time variables, plus the ones that are runtime-only (set after the build, in the env variables page). A variable set in the env variables page is available to both build and runtime. A variable set only at runtime (via Vercel’s API or CLI) is not available to the build.
The trap: a variable set after the build does not trigger a rebuild. The new value is available to the next runtime invocation, but the build that produced the static output is still using the old value. The fix: redeploy after changing env variables that the build needs.
The right pattern: set the variables before the first deploy, leave them alone in production, and use the API for runtime-only updates. The variables that change often (feature flags, rate limits) should be runtime-only, fetched by the function from a config service, not baked into the build.
The Next.js-specific gotchas (NEXT_PUBLIC_ and $env)
Next.js has a convention: any env variable prefixed with NEXT_PUBLIC_ is inlined into the client bundle and available to the browser. The rest are server-only. The Vercel env var set is the same on the Vercel side, but the inlining happens at build time, in the Next.js compiler.
// Server-side only (works on the server, not in the browser)
const apiKey = process.env.API_KEY; // server-only
// Client-side accessible (inlined into the bundle)
const publicUrl = process.env.NEXT_PUBLIC_API_URL; // available in the browser
The trap: a NEXT_PUBLIC_ variable that contains a secret is inlined into the client bundle, which means it is shipped to the browser and visible to anyone who opens the site. The “sensitive” flag in Vercel does not change this — Next.js still inlines the value. Sensitive only affects at-rest encryption, not the build-time inlining.
The right rule: never put a secret in a NEXT_PUBLIC_ variable. The value is in the JavaScript the browser downloads. The fix is to proxy the secret through a serverless function, which keeps the secret on the server.
For SvelteKit, the equivalent is $env/static/public and $env/dynamic/public. For Astro, it is import.meta.env.PUBLIC_*. The pattern is the same across frameworks: the public-prefixed variables are inlined, the rest are server-only.
For SvelteKit, the modern API is the $env module:
import { PUBLIC_API_URL } from '$env/static/public'; // client + server
import { API_KEY } from '$env/static/private'; // server only
The static/ prefix means “this is inlined at build time.” The dynamic/ prefix means “this is read at runtime, supports updates without rebuild.” The right choice is dynamic for variables that change often, static for values that are pinned at build time.
The secrets pattern that scales
For a team that has outgrown “put the secret in the Vercel dashboard”:
-
Use Vercel env variables for non-secret configuration (feature flags, public URLs, version strings). The values are fine to be visible to anyone with project access.
-
Use Vercel Sensitive env variables for first-party secrets (the database connection string, the OAuth client secret, the API keys for services the team uses directly). The at-rest encryption is the right shape for a secret that does not leave the Vercel ecosystem.
-
Use a third-party secret store (Doppler, 1Password, AWS Secrets Manager, GCP Secret Manager) for everything else (third-party API keys, customer-specific secrets, secrets that need to be shared across multiple platforms). The integration is a build step that fetches the secret and exports it to the build.
-
Use the Vercel CLI for local development, not the Vercel dashboard.
vercel env pull .env.localdownloads the project’s env variables to a local file, which is the right way to keep the local dev server in sync with the production deploy. The.env.localfile is gitignored and never committed. -
Audit the env variables regularly. A variable that was set for a one-off experiment and never removed is a variable that a future engineer will find and use, often for the wrong purpose. The audit is “list every env var, confirm it is still needed, remove the rest.”
For a team that wants the platform to manage the secret store, the MCP server pattern extends to a secrets tool that gives AI agents scoped access to the env variables they need without exposing the entire store. The same approach works for non-AI pipelines: a small service that the build calls to fetch the secret it needs, with the service doing the audit logging.
The hosting calculator at RunxBuild hosting calculator is the way to estimate the cost of the same workload on a platform that includes the env variable and secret management in the price, rather than as a third-party add-on.
FAQ
How do I set a Vercel environment variable?
In the Vercel dashboard, go to Project > Settings > Environment Variables. Add a name, a value, the environments it should apply to, and whether it is sensitive. Save, then redeploy for the new value to take effect.
What is the difference between sensitive and regular env variables?
Sensitive variables are encrypted at rest, and the plaintext is never shown in the dashboard. Regular variables are stored in plain text and visible to anyone with project access. Both are decrypted at build and runtime and exposed as normal env variables to the build and runtime.
How do I use env variables in a Next.js app?
Server-side: process.env.MY_VAR. Client-side: the variable must be prefixed with NEXT_PUBLIC_ to be inlined into the bundle. The prefix is the only thing that controls client-side access; the “sensitive” flag in Vercel does not affect inlining.
How do I update an env variable without a redeploy?
For runtime-only variables (read by a serverless function, not by the build), you can update via the Vercel API or CLI and the next invocation will see the new value. For build-time variables (read by the build process), you must redeploy after changing the value.
Can I use the same env variable in production and preview with different values?
Yes. Set the variable in both Production and Preview environments with different values. Vercel serves the right value to the right deploy automatically. This is the right pattern for any secret that should not be reachable from a feature branch.
Should I commit the .env file to the repo?
No. The .env file (and .env.local, .env.production, etc.) should be gitignored. The production values live in the Vercel dashboard or in a third-party secret store. The local file is for local development only.