All posts

Field guide

How to store API keys safely in a React or Next.js app

The honest answer surprises people: you can't store a secret API key in a React or Next.js frontend at all, because everything the browser gets is readable. Here is where keys actually belong, and the one pattern that keeps them safe.

Last updated

This question comes up constantly, and the useful answer is a reframe: you do not store a secret API key in a React or Next.js frontend, because you cannot. Everything shipped to the browser, code, environment values, network calls, is readable by anyone who opens the developer tools. So the real question is not where to hide the key, but whether the key belongs in the browser at all.

First, which kind of key is it?

Two categories, and they have opposite rules:

  • Public-by-design keys — a Stripe publishable key, a Supabase anon key, a Firebase config. These are built to live in the client. Ship them; that is what they are for.
  • Secret keys — a Stripe secret key, a server API key, a service-role key, anything that grants privileged access. These can never be in the browser, in any form.

If it is a public key, you are done: put it in a client-exposed variable and move on. The rest of this is about secret keys.

Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.

See a live scan →

Where a secret key belongs: on a server

A secret key lives in a non-public environment variable and is used only in server code. When the browser needs something that requires the key, it calls your server, and the server uses the key:

// Next.js — app/api/weather/route.ts (server only)
export async function GET(req: Request) {
  const key = process.env.WEATHER_API_KEY;   // secret, never NEXT_PUBLIC
  const city = new URL(req.url).searchParams.get("city");
  const data = await fetch(`https://api.weather.com/?key=${key}&city=${city}`)
    .then(r => r.json());
  return Response.json(data);                 // client gets data, not the key
}
// the React component calls YOUR endpoint, never the third party directly
const res = await fetch(`/api/weather?city=${city}`);
const data = await res.json();

The key never enters the bundle, so there is nothing to leak. In a plain React app with no built-in backend, the same pattern applies: you add a small server endpoint you own and proxy through it.

The two traps to avoid

First, do not reach for the NEXT_PUBLIC or VITE prefix on a secret to make the client see it; that publishes it, as covered in why env-var secrets still leak. Second, do not hardcode the key as a fallback; keep it in the environment and read it server-side. The underlying idea is client-side vs server-side secrets.

Frequently asked questions

Can I hide an API key in a React app?

No. Anything shipped to the browser is readable by anyone, no matter how you obfuscate, minify, or store it. A secret key in a React frontend, whether in code, an environment variable used client-side, or local storage, is public. The only way to keep a secret key secret is to never send it to the browser, which means using it on a server.

But some keys are meant to be in the frontend, right?

Yes, and telling the two apart is the whole game. Publishable and public-by-design keys, a Stripe publishable key, a Supabase anon key, a Firebase config, are built to be in the client and are safe there. Secret keys, the ones that grant privileged access, are not. If a key would let someone act as your server or account, it stays server-side.

How do I call a third-party API that needs a secret key?

Proxy it through your own server. Your React frontend calls your backend, your backend uses the secret key to call the third-party API, and only the result comes back to the browser. The key never leaves the server. In Next.js this is a route handler or server action; in a plain React app it is a small backend endpoint you own.

Is an environment variable enough to keep a key safe?

Only for keeping it out of your git history. An environment variable does nothing about the browser: if you reference it in client code or prefix it for the client (NEXT_PUBLIC or VITE), the value gets baked into the bundle. Env vars keep secrets out of source control; keeping them out of the browser is a separate decision about where you use them.

Check what your app is shipping

The mistake is invisible in your editor and only shows up in the built bundle. A scan reads what your deployed app actually serves and flags a secret key that made it into the browser. Run a free scan and see whether any key is exposed in your frontend.

Prbl scans your live app or your codebase for exactly the kinds of issues above.

How to Store API Keys Safely in a React or Next.js App: Prbl