NEXT_PUBLIC_ and VITE_ variables are public. Keep secrets out of them.
Not every .env leak needs a misconfigured server. Front-end build tools read .env too, and every variable with a public prefix is shipped to the browser — inlined into the JavaScript bundle or the page itself, where any visitor can read it in DevTools.
Which variables reach the browser
| Framework | Public prefix | Read in client code as |
|---|---|---|
| Next.js | NEXT_PUBLIC_ | process.env.NEXT_PUBLIC_… |
| Vite (Vue, React, Svelte…) | VITE_ | import.meta.env.VITE_… |
| Create React App | REACT_APP_ | process.env.REACT_APP_… |
| Nuxt | NUXT_PUBLIC_ | useRuntimeConfig().public |
| SvelteKit | PUBLIC_ | $env/static/public |
| Astro | PUBLIC_ | import.meta.env.PUBLIC_… |
| Gatsby | GATSBY_ | process.env.GATSBY_… |
The prefix is the framework saying “this one is public”. Obfuscation doesn’t change that: if the browser can read a value, so can everyone.
What is safe to expose, and what never is
- Safe: keys designed for browsers — Stripe publishable keys (
pk_live_…), Firebase web config, Supabase anon keys used with Row Level Security, analytics IDs, and Maps keys restricted to your domains. - Never: Stripe secret and restricted keys (
sk_live_…,rk_live_…), database URLs, Supabaseservice_rolekeys (they bypass Row Level Security), AI provider API keys, AWS secret keys, JWT signing secrets and SMTP passwords.
Check your build
DotenvScan checks your server for config files; it doesn’t read your JavaScript. Search the build output instead, after a production build:
grep -rnoE '\bsk_live_[A-Za-z0-9]{4}|\bAKIA[A-Z0-9]{12}|-----BEGIN [A-Z ]*PRIVATE KEY' .next/static dist build 2>/dev/null
Add the key prefixes of your own providers. On the live site, open DevTools → Sources and search all files (Ctrl+Shift+F, or ⌘⌥F on a Mac) for the same strings.
If a secret shipped
- Rotate it. It is in every cached copy of your bundle — browsers, CDNs, web archives. Removing it from the code doesn’t take those back.
- Move the call to the server — a Next.js Route Handler or Server Action, a SvelteKit server route, a serverless function — that reads the unprefixed variable and returns only what the page needs.
- Guard server-only code. In Next.js,
import 'server-only'at the top of a module makes the build fail if client code imports it. - Check the provider’s usage logs for calls you didn’t make.
Server-side .env files leak too — the four usual ways. Check yours with the scanner.