An environment variable keeps a secret out of your source code and git history, but it does not make the secret private if the code that reads it runs in the browser. Anything used in client-side code, including any value a framework marks as public, is bundled and shipped to every visitor. A secret is only hidden if it is read and used on the server.
What env vars actually do
They move the value out of committed source. That solves the git-history leak, which is real and worth solving, but it is only half the problem. Where the value is used matters as much as where it is stored.
The browser is public
In Next.js, a NEXT_PUBLIC_ prefix inlines the value into the client bundle. In Vite, import.meta.env values used in components ship too. Any secret referenced in client code can be read from the browser, no matter how you stored it. Secret keys must be used only in server code.
Does the secret actually stay private?
| Where the value is read | Exposed to the browser? |
|---|---|
| Server component, route handler, or server action | No, it stays on the server |
| Client component with a NEXT_PUBLIC_ prefix | Yes, inlined into the bundle |
| Client component using Vite import.meta.env in the UI | Yes, shipped to every visitor |
| Env var that is set but only read in server code | No, safe |
What this means for AI-generated code
AI assistants often fix a client-side undefined-variable error by adding a public prefix, which quietly ships the secret to the browser. The code works, so the leak is invisible until someone reads the bundle. Keep secret keys in server components, route handlers, or server actions only.
Common questions
If I put a secret in an environment variable, is it safe?
Only if the code that reads it runs on the server. An environment variable keeps the value out of your source and git history, but if a client component reads it, the value is bundled and shipped to the browser where anyone can read it.
What does the NEXT_PUBLIC_ prefix do?
It tells Next.js to inline that environment variable into the client-side JavaScript bundle so browser code can use it. That makes it public. Only put values on it that are safe for everyone to see, never a secret or service_role key.
How can I tell if a secret shipped to the browser?
Open your deployed app, view the page source or the network tab, and search the JavaScript for the value or its prefix (like sk_ or eyJ). If you can find it in the browser, so can anyone else. A scanner like Prbl checks this for you.
Where should a secret key be used in a Next.js app?
In server-only code: server components, route handlers under app/api, or server actions. These run on the server and never send their variables to the browser, so a secret read there stays private.