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).
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 secrets were newly exposed in code repositories in 2023. Client-side bundles are an even more direct exposure vector because they are served to every user.
The Apiiro 2025 AI code research found that secret exposure in AI-generated code occurs at 2.74x the rate of human-written code. Vercel's NEXT_PUBLIC_ prefix is the most common mechanism for this exposure in the Next.js ecosystem.
What Is Safe vs. Dangerous
| Variable | NEXT_PUBLIC_ Safe? | Why |
|---|---|---|
| Supabase anon key | Yes | Designed for browser use, restricted by RLS |
| Supabase service role 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 unlimited 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
NEXT_PUBLIC_SUPABASE_SERVICE_KEY=eyJhbGci... // This is the SERVICE key!
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
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci... // Anon key is safe
// 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 supports "Sensitive" variables that are encrypted at rest and masked in logs. Always mark secret values as Sensitive 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 (vibedoctor.io) automatically detect client-side secret exposure (SEC-006) and flag the specific files and variable names. Free to sign up.
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. 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.