Are NEXT_PUBLIC_ environment variables public? Yes. Which keys are safe in Next.js and how to move secrets to the server
Short answer: yes. Next.js copies every NEXT_PUBLIC_ value into the JavaScript your visitors download when you run next build. Publishable keys belong there; secret keys don't. To see what your own live site ships, run a free SniffPup scan (SniffPup is a free passive security scanner for vibe-coded apps). Only check sites you own.
Home › Guides › Are NEXT_PUBLIC_ variables public? · Last updated · By Ember, SniffPup
The fast way: a free SniffPup scan runs 15 checks from the outside, including a read of your page and up to 20 same-site JavaScript files (plus the chunks they load) for OpenAI, Anthropic, Stripe, Supabase service_role and other secret keys (masked in the report). It also flags NEXT_PUBLIC_ variable names that sound server-only, like NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY, and checks for public .env files and source maps. No signup.
A 100 means no issues were found in our checks. It is never proof of security.
1. Why NEXT_PUBLIC_ variables end up in the browser
Per the Next.js environment variables docs, variables without the NEXT_PUBLIC_ prefix are only available in the Node.js environment. Adding the prefix tells Next.js to "inline" the value at build time, replacing every process.env.NEXT_PUBLIC_... reference with the hard-coded value in the JavaScript bundle sent to the browser. The prefix is the "ship this to every visitor" switch.
The same docs note that the values are frozen when you run next build, so changing them later means rebuilding. And keeping a value in .env.local doesn't make it private if it has the prefix. This is a common way an AI-built Next.js app leaks a key: the AI writes NEXT_PUBLIC_OPENAI_API_KEY so a Client Component can call OpenAI directly, and it works for everyone who opens DevTools too.
2. Sort your keys: public by design vs secret
- Fine in NEXT_PUBLIC_: the Supabase anon or publishable (
sb_publishable_) key, with Row Level Security on for every exposed table, and the Stripe publishable key (pk_live_orpk_test_). These are meant to be public; their safety comes from rules elsewhere. - Never in NEXT_PUBLIC_: the Supabase
service_roleor secret (sb_secret_) key, which bypasses RLS; the Stripe secret key (sk_live_) and restricted keys (rk_live_); and OpenAI, Anthropic, xAI, Groq, OpenRouter or other AI provider keys, which spend your money.
Quick test: if the provider's dashboard calls it "secret", or it can spend money or read every row, it doesn't get a NEXT_PUBLIC_ prefix. More on the Supabase side in is the Supabase anon key safe to expose?
3. Move the secret into a Route Handler or server-only code
Rename the variable without the prefix (for example OPENAI_API_KEY) in .env.local and in your host's dashboard, then read it only where code runs on the server:
- A Route Handler: an
app/api/.../route.tsfile (see Next.js Route Handlers) readsprocess.env.OPENAI_API_KEY, calls the provider and returns only the result. Your Client Component calls/api/...instead of the provider. In the Pages Router, an API Route does the same job. - Server-only code: Server Components and Server Actions can read unprefixed variables too. Put the code that uses the key in its own file and add
import 'server-only'at the top. Per the Next.js docs on preventing environment poisoning, importing that file into a Client Component then fails the build, and unprefixed variables read in client code become an empty string instead of the secret.
The server code should check who is calling (a signed-in user, a rate limit) before using the key. A paste-ready prompt for your AI builder: "Move every secret API key out of NEXT_PUBLIC_ variables into Route Handlers or server-only modules marked with import 'server-only', keep only publishable keys public, and have client components call the server route instead."
4. Already shipped a secret? Rotate it
Removing the prefix isn't enough: old bundles get cached and crawled. Create a new key, store it without the prefix, deploy (remember NEXT_PUBLIC_ values only change on a new build), then revoke the old key and check the provider's usage page. The step-by-step version is in leaked API key: what to do, with guides for OpenAI, Stripe and the Supabase service_role key.
5. Check what your live site actually ships
Open your live site, open DevTools, and search all loaded files under /_next/static/ for sk-, sk_live_, sb_secret_ and service_role. Or let the free SniffPup scan do it. The exposed API keys guide covers the manual search, and the VITE_ version of this guide covers Vite, Lovable and Bolt apps.
FAQ
Are NEXT_PUBLIC_ environment variables public?
Yes. Next.js's docs say that prefixing a variable with NEXT_PUBLIC_ tells it to inline the value, at build time, into the JavaScript bundle delivered to the browser. Anyone who opens your site can read it in DevTools. Variables without the prefix are only available on the server.
Is it safe to put my Supabase anon key in NEXT_PUBLIC_SUPABASE_ANON_KEY?
Yes, the Supabase anon or publishable key is meant to be public, as long as Row Level Security is on for every table the API can reach. The service_role or secret key is different: it bypasses RLS and must never get a NEXT_PUBLIC_ prefix.
Is the Stripe publishable key safe in NEXT_PUBLIC_?
Yes. The Stripe publishable key (pk_live_ or pk_test_) is built for the browser. The secret key (sk_live_) and restricted keys (rk_live_) belong only in server code, such as a Route Handler.
Can I change a NEXT_PUBLIC_ value without rebuilding?
No. Next.js freezes NEXT_PUBLIC_ values at build time, so changing the variable in your host's dashboard does nothing until you build and deploy again. That matters when you replace a leaked key: redeploy after you change it.
My secret key was in a NEXT_PUBLIC_ variable. Is renaming it enough?
No. Every visitor and crawler that loaded the old bundle may have a copy. Create a new key in the provider's dashboard, store it without the prefix, read it only in server code, deploy, then revoke the old key and check the provider's usage page for calls you didn't make.
Does a clean SniffPup report mean my env variables are safe?
No. A score of 100 means no issues were found in our checks. It is never proof of security: the scan reads what your live site serves to a visitor, not your repo, your server code or pages behind a login.
Sources: Next.js environment variables, Next.js server and client components (preventing environment poisoning) and Next.js Route Handlers docs, checked October 31, 2026.
A score of 100 means no issues were found in our checks. It is not proof that your app is secure.