Guides

How to hide an API key in React, Next.js, Vite and Expo

Why NEXT_PUBLIC_, VITE_ and EXPO_PUBLIC_ variables end up in the browser, and the two safe ways to call an API from a web or mobile app.

Updated October 2026

Anything your website or mobile app ships to a device can be read by its user. There is no way to hide a secret key in browser code: minifying it, splitting it or putting it in an environment variable does not help. Researchers scanning ten million websites found more than 1,700 live API keys in their JavaScript.

Why environment variables do not hide it

Frameworks copy some environment variables into the code they send to the browser, on purpose, so the page can use them:

  • Next.js: variables starting with NEXT_PUBLIC_.
  • Vite (React, Vue, Svelte): variables starting with VITE_.
  • Create React App: variables starting with REACT_APP_.
  • Expo and React Native: variables starting with EXPO_PUBLIC_, and anything in the app bundle.

If NEXT_PUBLIC_OPENAI_API_KEY or VITE_STRIPE_SECRET_KEY exists, the key is in your bundle. Check with the free leak check, and if it is there, replace it now.

Publishable keys, such as Stripe's keys starting with pk_, are made for browsers. Secret keys never are.

Way 1: call the API from your server

The page calls your own server; your server holds the key and calls the provider. In Next.js, a route handler does it:

app/api/chat/route.ts
// Runs on the server only: OPENAI_API_KEY (no NEXT_PUBLIC_) never reaches the browser.export async function POST(request: Request) {  const { message } = await request.json()  const res = await fetch("https://api.openai.com/v1/chat/completions", {    method: "POST",    headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "content-type": "application/json" },    body: JSON.stringify({ model: "gpt-5-mini", messages: [{ role: "user", content: message }] }),  })  return new Response(res.body, { status: res.status })}

This keeps the key off the device, but your route is now open to anyone: a script can call it as often as it likes and spend your budget. Add sign-in, a limit per user, and a limit on what the route may ask for.

Way 2: a key made for the browser

With Vallkey, the page can call the API directly with a ghost key that only works from your websites, with a limit per visitor, for the endpoints and models you allow, within a daily budget. Even if someone copies it from the page, it cannot be used anywhere else or for anything more.

  • Allowed websites: requests from other sites are refused.
  • Limits per visitor, per minute and per day, so one person cannot spend the whole budget.
  • Short-lived keys for signed-in users, which your server hands out and which expire on their own.

See keys for websites and mobile apps in the docs, or start in five minutes.

Make your next leaked key harmless

Give your app a ghost key that only works within your rules, with a budget and a kill switch. Free plan, no card needed.