How to prevent leaking API keys in frontend apps

If a string is bundled into JavaScript that ships to the browser, you must assume it is public. Minification and “don’t document it” do not hide it. Tools exist to search bundles for high-entropy strings; users can open DevTools → Sources or download the built chunk.

This article is framework-oriented but the rule is universal: server secrets stay on the server.


1. The non-negotiable rule

Never put server-side API keys, database URLs, or signing secrets in:

  • NEXT_PUBLIC_* (Next.js)
  • VITE_* (Vite)
  • Create React App REACT_APP_* legacy prefix
  • Any variable your build tool inlines into client JS

Next.js documents which env vars are exposed: Environment Variables — behavior evolves; always verify your major version.


2. What can live in public env vars?

Only non-secret configuration:

  • Public site URL
  • Analytics IDs meant to be visible (still consider abuse)
  • Feature flags that are not security boundaries

When in doubt, treat it as secret and keep it server-only.


3. Safe architecture: Browser → Your backend → Third party

Browser  --(session cookie / JWT)-->  Your API route / server
Your server  --(SECRET_API_KEY)-->  Stripe / OpenAI / etc.

The third-party secret never appears in the browser. OWASP’s cheat sheet aligns: do not embed secrets in client-side code (Secrets Management).

Next.js App Router pattern (conceptual)

Use Route Handlers or Server Actions that run on the server only:

// app/api/chat/route.ts — server-only; do NOT prefix OPENAI_API_KEY with NEXT_PUBLIC_
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const key = process.env.OPENAI_API_KEY;
  if (!key) {
    return NextResponse.json({ error: "Server misconfigured" }, { status: 500 });
  }
  // call upstream with key — never return key to client
  return NextResponse.json({ ok: true });
}

Never return process.env or log env in client components.


4. Vite + SPA (no built-in server)

Pure SPAs need a backend somewhere:

  • Serverless functions (Netlify, Vercel, Cloudflare Workers) as BFF (“backend for frontend”).
  • Same-origin API under /api proxied in dev.

If you only have static hosting, you cannot safely hold a private OpenAI key — use a tiny server component.


5. Common leak paths beyond env prefixes

MistakeWhy it leaks
fetch to third party from browser with Authorization headerVisible in Network tab
Embedding Google Maps or similar keys without HTTP referrer restrictionsAbuse / quota theft — use provider key restrictions
Committing .env.local “just once”Git history — incident guide
Logging import.meta.env or process.env in clientConsole exposure

6. Local development

Keep OPENAI_API_KEY (etc.) in a gitignored .env.local or in PassStore and paste into server env — same rules as where to store .env.


7. Soft CTA

Developer secrets belong in a macOS vault with encrypted storage, not in src/. Download PassStore · Security model.


Related