Skip to content
Throttle Bird

Logbook · Launch notes

How to hide API keys in a React app

You can't hide a secret key inside a React app that runs in the browser, not even with a .env file. Here's which keys are fine to show, where the secret ones should go, and how to check yours.

2026-10-26 · Throttle Bird

This is one of the most searched questions from people building their first app, and the honest answer surprises most of them: you can't hide a secret key inside a React app. Not with a .env file, not with a special prefix, not by splitting it into pieces.

That sounds like bad news. It isn't, once you know where the key should live instead. This guide covers why it works this way, which keys are actually fine to show, and how to move the secret ones somewhere safe.

Why a .env file doesn't hide anything

A React app runs in your visitor's browser. To run there, all of its code gets downloaded to their computer. Anything your React code can read, the visitor can read too.

A .env file feels private because it isn't in your code. But when you build the app, the build tool copies those values into the JavaScript files it produces. The tools say this openly:

  • Vite (used by Lovable, Bolt, and many others) exposes any variable starting with VITE_ to your app's code. Vite's docs warn that these end up in the client bundle and shouldn't contain anything sensitive.
  • Create React App does the same with REACT_APP_, and its docs say not to store secrets this way.
  • Next.js sends anything starting with NEXT_PUBLIC_ to the browser.

So VITE_OPENAI_API_KEY isn't hidden. It's published. Anyone can open your site, open the browser's developer tools, and search the JavaScript for it.

Which keys are fine to show

Not every key is a secret. Some services give you a key that is designed to be public, and protect your account in other ways. These are generally fine in a React app:

  • The Supabase anon or publishable key. Your database rules protect your data, not the key.
  • The Firebase web config, including its apiKey. Firebase security rules protect your data.
  • A Stripe publishable key (starts with pk_).
  • Map, analytics, and captcha keys meant for the browser, as long as you restrict them to your domain in that provider's dashboard.

Which keys must never be in the browser

These give whoever holds them real power over your account or your bill:

  • AI provider keys: OpenAI, Anthropic, and similar
  • A Stripe secret key (starts with sk_live_ or sk_test_)
  • The Supabase service_role key or a secret key starting with sb_secret_
  • Email, SMS, GitHub, Slack, or cloud (AWS) keys
  • Database passwords and private keys

If one of these is in a variable your React code reads, treat it as public.

Where secret keys should go instead

The fix is always the same idea: put a small piece of server code between your app and the service. Your React app asks your server, your server adds the secret key and calls the service, and the visitor only ever sees the answer.

You don't need to run a server yourself. Depending on how you built the app, one of these is probably already available:

  • Supabase Edge Functions. Store the key as a secret in your Supabase project and call the function from your app. This is the usual route for Lovable apps.
  • Netlify Functions or Vercel Functions. Store the key as an environment variable without a public prefix, and read it only in the function.
  • Next.js route handlers or server actions. Use a variable without NEXT_PUBLIC_ and only read it in server code.
  • Your builder's own secrets tool, such as Replit Secrets or Base44 Secrets with backend functions.

If you built with an AI tool, this prompt usually gets it right:

Find every API key used in the frontend code, including anything read from VITE_, REACT_APP_, or NEXT_PUBLIC_ variables. For each one, tell me whether it's meant to be public. Move every secret key to a server-side function, store it as a server-only secret, and have the frontend call that function instead. Then confirm no secret key appears in the built JavaScript.

Don't stop at moving the key

A server function that calls OpenAI for anyone who asks is just a slower way to share your key. Before you launch, make sure the function:

  • Checks that the person calling it is signed in, if the feature is for signed-in users.
  • Has a basic rate limit, so one person can't send thousands of requests.
  • Has a spending limit set in the AI provider's dashboard, as a backstop.

How to check your own app

  1. Open your live site in Chrome.
  2. Open developer tools (right-click, then Inspect), go to the Sources tab, and press Ctrl+Shift+F (Cmd+Option+F on a Mac) to search all files.
  3. Search for sk-, sk_live_, service_role, and sb_secret_.

If anything turns up that matches a secret key, two things need to happen. Move it to a server, as above. And make a new key in that service's dashboard and delete the old one, because the old one may already have been copied. Moving a key doesn't un-publish it.

I wrote a longer explanation of what to do after a leak in What is an exposed API key.

The short version

  • Anything a React app can read, a visitor can read. A .env file doesn't change that.
  • Public-by-design keys (Supabase anon, Firebase config, Stripe pk_) are fine. Secret keys aren't.
  • Put secret keys in a server function and have your app call it.
  • If a secret key was ever public, make a new one.

The free Throttle Bird scan searches your live page and its JavaScript for the secret key formats above (Stripe, OpenAI, Anthropic, Supabase secret and service_role keys, GitHub, Slack, AWS, private keys) and tells you which public keys it found are fine. It reads only what any visitor's browser downloads.

Sources: Vite: Env variables and modes, Create React App: Adding custom environment variables, Next.js: Environment variables, Supabase: Edge Function secrets.

Before you go: the free launch-safety checklist

Five practical steps to check before you launch, with a prompt for each. Free page and PDF, or we'll email it to you.

Check your own app

Paste your URL to run these checks automatically. Free, results in under a minute.

Scan my app free