1004 words
5 minutes

Vite VITE_ Variables Expose Secrets: Keep Private Values Out of the Bundle

2026-08-16
Frontend
Vite
/
Frontend
/
JavaScript
/
Troubleshooting

Treat every VITE_* variable as public. Vite replaces exposed variables into the client bundle at build time, so visitors can download the value from JavaScript or inspect it in browser developer tools. API secrets, database passwords, signing keys, and private tokens must stay on a server, serverless function, or edge endpoint instead.

The fix is not to hide the .env file more carefully. Decide which values are safe for a browser, expose only those with the intended prefix, move private operations behind an authenticated server boundary, and rebuild after changing the configuration.

Separate public configuration from private secrets#

A safe .env file makes the boundary visible:

# Safe only if the URL is intended for every browser visitor
VITE_API_ORIGIN=https://api.example.com
# Server-only values: do not add the VITE_ prefix
PAYMENTS_SECRET=replace-me-on-the-server
DATABASE_URL=postgres://user:[email protected]/app

Client code can read the public value through import.meta.env:

const apiOrigin = import.meta.env.VITE_API_ORIGIN;
fetch(`${apiOrigin}/public-config`);

This is not a permission system. The VITE_ prefix tells Vite that a value may be exposed to client-side code; it does not encrypt or protect the value. A value without the prefix is not automatically usable by a browser, but a server-side process can read it through its own runtime configuration.

The Vite environment and mode guide explicitly warns that VITE_* values should not contain sensitive information. Treat a public API key as public too: its presence in a bundle may be expected, but its permissions and server-side restrictions still need to be correct.

Find an exposed value in dist#

If you suspect a secret was bundled, inspect the generated output without printing the secret into CI logs. Search for a distinctive non-sensitive fragment or the variable name first:

Terminal window
pnpm exec vite build
rg -n --hidden 'PAYMENTS_SECRET|DATABASE_URL|PRIVATE_TOKEN' dist

Searching for the variable name can miss a value that Vite replaced directly. For an incident review, download the exact deployed JavaScript and search it locally for a short, non-sensitive marker. Never paste a live token into a command copied to a shared build log.

Also inspect source maps if they are published. A production source map can reveal source code and string literals even when the main bundle is minified. Removing the prefix from a later build does not remove a token from an already deployed asset or from a previous build artifact.

If a real secret was exposed, rotate or revoke it before cleaning up the code. Then remove it from client configuration, rebuild from a clean source of truth, purge the old deployment or cache if your host requires it, and check the deployed assets again.

Use a server or edge proxy for private API calls#

The browser should call an endpoint that you control; that endpoint can attach the private credential when it calls the upstream service:

browser -> /api/report -> server or edge function -> private API
reads PAYMENTS_SECRET

The client needs only a public route or origin:

const response = await fetch('/api/report', {
headers: { 'content-type': 'application/json' },
method: 'POST',
body: JSON.stringify({ range: 'week' }),
});

The server-side handler should validate the user, authorization, input, and upstream response. Do not turn the proxy into a blind URL forwarder; otherwise moving the token to the server only hides the credential while leaving the upstream capability unrestricted.

If a value genuinely must change at runtime for every browser, return only that public value from a runtime configuration endpoint or server-rendered page. A static Vite bundle cannot read a new host environment variable after vite build has finished. The Vite production undefined guide covers that separate build-time loading problem.

Review envPrefix before changing it#

Vite exposes VITE_ by default. A project can configure another prefix in vite.config.ts, but the setting should narrow the contract rather than expose more of the process environment:

import { defineConfig } from 'vite';
export default defineConfig({
envPrefix: ['PUBLIC_'],
});

Now only the selected public prefix is intended for client exposure. Keep server-only names such as DATABASE_URL and PAYMENTS_SECRET outside that prefix.

Do not set envPrefix to an empty string or a broad value to “make every variable available.” The Vite shared options reference warns that this can expose sensitive environment variables. Review envPrefix as part of a security change, not as a shortcut for fixing an undefined value.

Check mode and build timing#

Vite loads .env, .env.local, and mode-specific files such as .env.production or .env.staging. The selected mode and existing process environment determine which value reaches the build:

Terminal window
pnpm exec vite build --mode staging

For a static deployment, the important sequence is:

build environment -> vite build -> HTML and JavaScript assets -> CDN

Changing VITE_API_ORIGIN in a hosting dashboard after the asset build does not rewrite the already generated JavaScript. Rebuild and redeploy, then verify the final asset. If you need live private configuration, use a server or edge runtime instead of adding another public prefix.

Before merging, review three things:

  1. Does the variable need to be known by the browser, or only by a server?
  2. Does the name match the intended public prefix and selected mode?
  3. Does the generated dist contain only values that are safe for every visitor?

The rule to remember is that VITE_ means “ship this to the browser.” If the value would be a security incident when copied into a public gist, it does not belong in a Vite client environment variable.

FAQ#

Is a VITE_API_KEY secret?#

No. The VITE_ prefix marks the value for client exposure, and Vite replaces it into browser assets during the build. Use it only for a value that is safe for every visitor to see.

Why does removing VITE_ make the value undefined?#

Vite intentionally filters client environment variables by prefix. Removing the prefix protects the browser boundary, but the private value must then be read by a server-side process rather than by client code.

Can I hide a Vite secret with a custom envPrefix?#

No. A custom prefix changes which names are exposed; it does not make an exposed value private. Use a narrow public prefix and keep secrets in server or edge runtime configuration.

How do I change a Vite environment value without rebuilding?#

You cannot change a value already replaced into a static bundle without generating new assets. Use a runtime endpoint or server-rendered configuration for safe public values, and keep private values on the server.

References:

Vite: Env Variables and Modes

Vite: Shared Options — envPrefix

Vite VITE_ Variables Expose Secrets: Keep Private Values Out of the Bundle
https://laplusda.com/en/posts/vite-env-secret-exposure/
Author
Zero
Published at
2026-08-16
License
CC BY-NC-SA 4.0
Was this article useful?

Report a typo or broken link, or suggest a related topic.