Milo Kits
Guide

Is my API key exposed in my JavaScript? VITE_ and NEXT_PUBLIC_ explained

If your code reads an API key through a variable starting with VITE_, NEXT_PUBLIC_ or REACT_APP_, that key is in your public JavaScript and anyone can copy it. Putting it in an environment variable keeps it out of your Git repo, but it doesn't keep it out of the browser: the build tool pastes the value into the bundle. Public-by-design keys (Supabase anon/publishable, Stripe pk_, Firebase web config) are fine there. Secret keys (OpenAI, Stripe sk_, Supabase service role, Resend, AWS and so on) must only be used on a server or in a serverless/edge function.

Why the prefix matters

Build tools deliberately expose prefixed variables to the browser:

Hardcoding a key in a component is the same problem with extra steps. If the browser runs the code, the browser has the key.

Safe in the browser vs secret

Usually fine in frontend codeNever in frontend code
Supabase anon / sb_publishable_ key (with RLS)Supabase service_role / sb_secret_
Stripe publishable key pk_live_…Stripe secret key sk_live_… or restricted key rk_…
Firebase web config (apiKey, projectId)Firebase Admin SDK service account JSON
Public analytics IDs, map keys restricted by domainOpenAI, Anthropic, Gemini, Resend, SendGrid, Twilio, AWS keys, database URLs

Sources: Supabase API keys, Stripe API keys, Firebase API keys. Firebase notes its web API key isn't used for authorization, but it also recommends restricting keys and keeping other, billable Google APIs off it.

How to check your live site in two minutes

  1. Open your live site, then DevTools (F12) → Sources. Press Ctrl/Cmd+Shift+F to search all files.
  2. Search for: sk-, sk_live, rk_live, service_role, sb_secret_, AKIA, re_ (Resend), SG. (SendGrid), -----BEGIN.
  3. Also check the Network tab: if the browser calls api.openai.com directly, the key is in the browser.
  4. Locally, after npm run build, search the output folder (dist/ or .next/) for the same strings.

The fix: call the API from the server

The browser calls your endpoint (an API route, serverless function or Supabase Edge Function), and that endpoint calls the third-party API with a key read from a server-only environment variable (no public prefix). Add a login check and a rate limit so your endpoint doesn't become a free proxy for strangers.

Move a secret key server-side
My [SERVICE, e.g. OpenAI] API key is exposed in my frontend (it's read from [VITE_/NEXT_PUBLIC_ VARIABLE] in [FILE]). Please: 1. Create a server-side endpoint (API route, serverless function or Supabase Edge Function, whichever fits my stack) that reads the key from a server-only environment variable with no public prefix. 2. Remove the key and the prefixed variable from all client code and bundled .env files. 3. Make the frontend call the new endpoint, require a logged-in user, and add a simple per-user rate limit. 4. Show me the diff and tell me exactly where to set the env var on my host.
Scan the project for leaks
Search this whole project, including .env files, config and the production build output, for secrets that could reach the browser: API keys, tokens, service_role / sb_secret_ keys, Stripe sk_/rk_ keys, private keys and database URLs. For each, tell me the file, whether it's bundled into client code, and whether it's public-by-design or must move server-side. Don't print full secret values, mask them.

"Public" keys still need limits

A key being fine in the browser doesn't mean it should be unlimited. A few cheap settings make a leaked or abused key much less painful:

Already leaked? Do this in order

  1. Rotate the key in the provider's dashboard (create a new one, delete the old one).
  2. Set a spending limit or usage alert if the provider supports it.
  3. Check the provider's usage logs for activity you don't recognise.
  4. Deploy the server-side fix so the new key never reaches the browser.
  5. If the key was ever committed to Git, assume it's public even after you delete the file, because it stays in the history. GitHub's secret scanning can warn you about this.

FAQ

Are environment variables secure in Vite or Next.js?

Only the ones without a public prefix, used in server-side code. Variables starting with VITE_ or NEXT_PUBLIC_ are deliberately copied into the JavaScript sent to every visitor, so they must only hold values that are safe to be public.

Is it OK to put my OpenAI API key in VITE_OPENAI_API_KEY?

No. Anything in a VITE_ variable ends up in your public bundle, and anyone can copy the key and use it on your bill. Call OpenAI from a server-side route or edge function that reads the key from a server-only variable.

How do I know if my API key is exposed?

Open your live site, then DevTools Sources, and search all files for key prefixes such as sk-, sk_live, service_role or sb_secret_. Also check the Network tab for direct calls from the browser to providers like api.openai.com.

Is my Stripe publishable key safe to expose?

Yes. Stripe publishable keys (pk_) are meant for the browser. The secret key (sk_) and restricted keys (rk_) must stay on the server.

I removed the key from my code. Is that enough?

No. Once a key has been public, assume someone has it. Rotate it in the provider's dashboard and check usage for anything unexpected.

Sources

This guide is general information, not a security audit. Platforms change, so check the linked docs for current details. Spotted something out of date? Email hello@milokits.com.

More guides