Vibe coding security
Which environment variables get exposed to the browser
Most frameworks decide what's public by naming convention: a specific prefix tells the build tool to embed a value into the client bundle, where anyone can read it. Here's the reference list — and what must never use one.
Short answer
Frameworks expose environment variables to the browser by prefix: a variable named with a recognized public prefix is bundled into your client JavaScript, where every visitor can read it. The common ones are NEXT_PUBLIC_, VITE_, REACT_APP_, PUBLIC_, EXPO_PUBLIC_, and GATSBY_. These are safe only for public identifiers — never put a secret key behind one.
Key takeaways
- Many frameworks expose environment variables to the browser by naming convention: a specific prefix tells the build tool to embed that value into the public client bundle.
- An exposed env variable is not encrypted or hidden — anyone can read it in the page's JavaScript. It's only safe for public identifiers.
- Common client-exposed prefixes include NEXT_PUBLIC_ (Next.js), VITE_ (Vite), REACT_APP_ (Create React App), PUBLIC_ (Astro/SvelteKit), EXPO_PUBLIC_ (Expo), and GATSBY_ (Gatsby).
- Never put a secret — a database service key, payment secret, or private API key — behind one of these prefixes. It will ship to every visitor.
- When in doubt, assume any value the frontend can read is public, and keep real secrets in server-side code or environment variables without a public prefix.
The client-exposed prefix reference
Each entry below is the prefix that a framework's build tooling treats as "ship this to the browser." Any variable using it becomes part of the public bundle.
NEXT_PUBLIC_
Next.js. Variables prefixed this way are inlined into the browser build; everything else stays server-only. Used by v0 and many AI-built Next.js apps.
VITE_
Vite. Exposed via import.meta.env in the client. This powers Lovable and many Bolt.new apps, so VITE_ is one of the most common places secrets leak.
REACT_APP_
Create React App. Any REACT_APP_ variable is embedded into the static build and visible in the browser.
PUBLIC_
Astro and SvelteKit. PUBLIC_-prefixed variables are exposed to client code; unprefixed ones are kept private to the server.
EXPO_PUBLIC_
Expo (React Native, including web). EXPO_PUBLIC_ variables are embedded into the app bundle that ships to devices and browsers.
GATSBY_
Gatsby. GATSBY_-prefixed variables are made available to browser code at build time.
NUXT_PUBLIC_ / public runtimeConfig
Nuxt. Values placed under the public runtime config (often via NUXT_PUBLIC_) are sent to the client; private config stays server-side.
PLASMO_PUBLIC_ / WXT_PUBLIC_
Browser-extension frameworks (Plasmo, WXT). Public-prefixed values are bundled into the extension's client code.
The list isn't exhaustive — the rule is
Tools change and new frameworks add their own conventions. The durable rule: any value the frontend can read is public. If you're unsure whether a variable ships to the browser, assume it does and keep real secrets out of it.
Safe to expose vs. never expose
A public prefix is correct for values that are meant to be seen, and dangerous for anything that grants privileges.
Safe behind a public prefix
A public API base URL or your app's own domain.
A Supabase project URL and the Supabase anon (public) key — designed to ship in the client.
A provider's publishable key (e.g. a Stripe publishable key) explicitly meant for the browser.
Non-sensitive feature flags and analytics site IDs.
Never behind a public prefix
A Supabase service_role key or any database admin/service key.
A Stripe secret key or other payment secret.
A private API key — OpenAI, email/SMS providers, or any third-party secret.
Webhook signing secrets, JWT signing keys, or any private token.
Secrets belong in server-side code — API routes, server actions, edge or serverless functions — using variables without a public prefix. For the broader picture, see public .env files & exposed secrets.
What to do if you exposed a secret
If a secret shipped behind a public prefix, treat it as compromised — it may already be in someone's cache or logs.
Rotate the key immediately in the provider's dashboard so the exposed value stops working.
Move the secret to a server-side variable without a public prefix, and reference it only from server code.
Redeploy and confirm the new bundle no longer contains the secret.
Run a scan of the live URL to confirm nothing else is exposed.
The full recovery steps are in public .env files and what to do if a secret leaked.
Frequently asked questions
- Which environment variable prefixes are exposed to the browser?
- The most common client-exposed prefixes are NEXT_PUBLIC_ (Next.js), VITE_ (Vite, including Lovable and many Bolt apps), REACT_APP_ (Create React App), PUBLIC_ (Astro and SvelteKit), EXPO_PUBLIC_ (Expo / React Native web), GATSBY_ (Gatsby), and NUXT_PUBLIC_ / runtime public config (Nuxt). Any variable with one of these prefixes is embedded into the public JavaScript bundle and is readable by every visitor.
- Is it safe to put an API key in a NEXT_PUBLIC_ or VITE_ variable?
- Only if the key is meant to be public. A Supabase anon key or a provider's publishable key is designed to be seen and is fine in a public-prefixed variable. A secret key — a database service_role key, a Stripe secret key, a private AI API key, or any token that grants real privileges — must never use a public prefix, because it will be bundled into the browser where anyone can read it.
- Why do frameworks expose some environment variables on purpose?
- Frontend code runs in the browser, so it sometimes needs configuration values — a public API URL, a publishable key, a feature flag. Frameworks use a naming convention (the prefix) so you explicitly opt a variable into the client bundle, and everything without that prefix stays server-only by default. The prefix is a safety boundary: it forces you to consciously mark a value as public.
- How do I check if I exposed a secret in my frontend?
- Search your code for any secret referenced through a public-prefixed variable, and open your deployed site's JavaScript bundle (via browser dev tools) to look for keys. A scan of your live URL can also flag secrets that shipped to the frontend. If a secret has already been exposed, rotate it immediately — the old value should be treated as compromised.
Check what your frontend is exposing
GuardMint scans your live app's bundle for keys and secrets that shipped to the browser — and flags the serious ones. No signup required for your first score.