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.
Related resources
How to scan your website for vulnerabilities
A plain-English guide to scanning a web app for common security issues before launch — what a scan checks, what it can't, and how to read the results.
Read moreHow to check if your web app is secure
A practical guide to checking whether a web app is secure enough to launch, combining automated scans with manual auth and data checks.
Read moreVercel security checklist
Environment variables, preview deployments, headers, redirects, and public build output before launch.
Read moreCheck 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.