Quick Answer
Any Vercel environment variable prefixed with NEXT_PUBLIC_ is bundled into your client-side JavaScript and visible to anyone who opens browser DevTools. Database URLs, Stripe secret keys, Supabase service role keys, and API secrets must never have this prefix. If they do, your secrets are already exposed in every browser that loads your site.
The NEXT_PUBLIC_ Problem
Next.js uses a simple naming convention to determine which environment variables are available in the browser: any variable starting with NEXT_PUBLIC_ is inlined into the client-side JavaScript bundle at build time. Variables without this prefix are only available in server-side code (API routes, getServerSideProps, Server Components).
The same build-time rule decides which API address your browser code calls. If that address is a hardcoded localhost URL instead of a variable, every call fails once the app is deployed.
This design is intentional and useful - your app needs some values in the browser (like a Supabase anon key or a Google Analytics ID). The problem is that AI coding tools do not distinguish between browser-safe and server-only secrets. According to GitGuardian's 2024 report, 12.8 million new secrets were leaked publicly on GitHub in 2023. Client-side bundles are an even more direct exposure vector because they are served to every user.
Apiiro's 2025 research found that AI-assisted developers exposed cloud credentials nearly twice as often as their non-AI peers. In a Next.js app, one easy way to expose a key is to give it the NEXT_PUBLIC_ prefix.
What Is Safe vs. Dangerous
| Variable | NEXT_PUBLIC_ Safe? | Why |
|---|---|---|
| Supabase anon / publishable key | Yes | Designed for browser use, restricted by RLS |
| Supabase service role / secret key | NO | Bypasses all RLS, full database admin access |
| Stripe publishable key (pk_live_) | Yes | Designed for client-side Stripe.js |
| Stripe secret key (sk_live_) | NO | Allows charges, refunds, customer data access |
| Google Analytics ID | Yes | Public tracking identifier |
| OpenAI API key | NO | Allows API usage billed to you |
| DATABASE_URL | NO | Direct database connection with credentials |
| App URL / site URL | Yes | Public information, no secret value |
| JWT_SECRET | NO | Allows forging authentication tokens |
| SendGrid / Resend API key | NO | Allows sending email as your domain |
How AI Tools Create This Problem
# ❌ BAD - AI puts secrets in NEXT_PUBLIC_ variables
# .env.local (what AI generates)
NEXT_PUBLIC_SUPABASE_URL=https://abc.supabase.co
# This is the SERVICE key!
NEXT_PUBLIC_SUPABASE_SERVICE_KEY=eyJhbGci...
NEXT_PUBLIC_STRIPE_SECRET=sk_live_abc123
NEXT_PUBLIC_OPENAI_KEY=sk-abc123
# ✅ GOOD - Only browser-safe values get NEXT_PUBLIC_
# .env.local (corrected)
NEXT_PUBLIC_SUPABASE_URL=https://abc.supabase.co
# Anon key is safe
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
# Server-only (no NEXT_PUBLIC_ prefix)
SUPABASE_SERVICE_KEY=eyJhbGci...
STRIPE_SECRET_KEY=sk_live_abc123
OPENAI_API_KEY=sk-abc123
How to Check for Exposed Secrets on Vercel
- Open your deployed site in a browser and go to DevTools > Sources tab.
- Search across all sources for strings like
sk_live,sk-,service_role, andeyJhbGci. - Check your Vercel dashboard > Settings > Environment Variables. Look for any variable with
NEXT_PUBLIC_prefix that contains a secret value. - Search your codebase for
process.env.NEXT_PUBLIC_and verify each usage is browser-safe.
Vercel-Specific Environment Variable Features
Vercel provides environment scoping that AI tools do not use. Variables can be set per environment (Production, Preview, Development), which prevents preview deployments from using production secrets. Vercel also lets you save a variable as a Secret (the type that replaced "Sensitive"): its value is write-only after saving, and Vercel redacts it from build logs when it is 32 characters or longer. Always save secret values as the Secret type in the Vercel dashboard.
What to Do If Your Secrets Are Already Exposed
- Rotate immediately. Generate new keys/secrets from each provider's dashboard (Supabase, Stripe, OpenAI, etc.).
- Update Vercel env vars. Remove the
NEXT_PUBLIC_prefix, add the new keys, redeploy. - Check access logs. Review your Stripe dashboard, Supabase logs, and OpenAI usage for unauthorized activity.
- Set up monitoring. Enable billing alerts on paid API services to catch unauthorized usage early.
- Scan your codebase. Tools like VibeDoctor's Vibe Check flag code that reads a public-prefixed variable with a secret-sounding name, such as
process.env.NEXT_PUBLIC_STRIPE_SECRET(SEC-006), and run Gitleaks to find key values committed in your files, each with its file and line. Free to sign up.
Your host can also leak environment variables you never exposed. In October 2026 an attacker read 3,218,965 environment variable records from the Zeabur hosting platform. Our Zeabur breach guide covers the order to rotate keys in and how to limit the next leak.
FAQ
Why does Supabase have two keys?
The anon key is designed for browser use and respects Row Level Security policies. The service role key bypasses all RLS and has full admin access to your database. The anon key is safe to expose; the service role key must only be used in server-side code. Newer Supabase projects use a publishable key and a secret key instead (anon and service_role are the legacy versions), and the same rules apply. AI tools frequently confuse the two.
Does removing NEXT_PUBLIC_ from a variable fix the exposure?
Removing the prefix prevents future exposure, but if the variable was already deployed with NEXT_PUBLIC_, the secret exists in every cached build artifact and browser cache. You must rotate the secret (generate a new one) and redeploy. The old secret should be considered compromised.
How do I use server-only env vars in Next.js?
Server-only variables (without NEXT_PUBLIC_ prefix) are available in Server Components, API routes (app/api/), getServerSideProps, and middleware. They are not available in Client Components or any code that runs in the browser. Access them with process.env.VARIABLE_NAME in server-side code only.
Does this apply to VITE_ prefix in Vite projects?
Yes, the same concept applies. Vite uses VITE_ prefix instead of NEXT_PUBLIC_, and any VITE_ variable is exposed in the client bundle via import.meta.env. The same rules apply: only browser-safe, non-secret values should use the VITE_ prefix.
What about Vercel Edge Functions - are env vars safe there?
Yes. Vercel Edge Functions run on the server (at the edge), so non-prefixed environment variables are safe there. They never reach the browser. Edge Functions are a good place for API calls that need secret keys.