Next.js + GeminiProvider key in client bundle

Gemini API key exposed in frontend code: how to secure AI calls

If a client-side page constructs a Gemini provider client with a `NEXT_PUBLIC_` key, assume the key can be recovered from the browser build when configured. Move provider calls to a server route, rotate the exposed key, authorize the requesting user, and put request, concurrency, and spend limits before each provider call.

This guide covers check 03: Secrets and key management; check 06: Performance and scalability in the Zenveus Production Readiness Standard.

For builders

What this means, in plain words

A Gemini key in browser code can be copied and used outside your app. A browser credit counter cannot control those direct calls. Rotate the exposed key and route paid generation through an authenticated server with quotas.

A scoped repair request

Using an AI builder? Paste this

Use this prompt in Lovable, Cursor, Replit, or Claude Code with the relevant server files available.

Audit my Gemini integration for keys in client components or public environment variables. Rotate exposed keys, move paid generation to an authenticated server route, and enforce input bounds, shared quotas, timeouts, and output limits before the provider call. Work on a branch with synthetic data and mocked external services. Show the smallest diff, identify required adapters and deployment settings, and add allowed and denied tests that prove side effects cannot happen before checks pass. Do not disable security checks to make a test pass.

The same failure may appear as

  • NEXT_PUBLIC Gemini key is visible in browser code
  • Client page calls Gemini directly
  • AI usage grows outside app quotas
  • A frontend usage counter can be bypassed

Find the failure layer

Run these checks before rewriting anything

Each check removes a class of causes. Keep the first failing result, its timestamp, and the production log beside it.

01

Provider imports

Follow imports into `"use client"` pages, browser utilities, or direct click handlers.

If this failsA paid provider client runs in the browser; move it behind an authenticated server route.
02

Deployed key

Check provider usage and restrictions for unexpected calls.

If this failsThe deployed bundle contains a configured credential; rotate it and replace all served builds containing it.
03

Server usage gates

A UI credit counter is insufficient if the provider call happens directly in the browser.

If this failsOnly browser counters enforce usage; add atomic server quotas and prove denied calls never reach a mocked provider.

Ranked diagnosis

Common root causes, in the order we would test them

01

Provider call runs in browser

The client page imports a Gemini helper initialized with a public-prefixed key.

02

Key is assumed private because it came from env

The prefix deliberately makes configured values available to the browser.

03

Quota exists only in UI state

Direct calls can bypass a local counter.

04

No pre-call budget gate

The app has no server checkpoint before provider spending.

Step-by-step repair

How to fix this in your app

Edit lib/gemini.ts or utils/AiModal.ts and any use client page that imports it. Move the provider call to app/api/generate/route.ts.

Production safety ruleNever disable access controls, expose service keys, or add wildcard CORS as a routine shortcut.

01

Replace and revoke the browser-exposed key

Create a replacement key in the provider project, configure it only on the server, and revoke the exposed key. Review usage and available API restrictions. Rebuilding without rotation leaves copied keys usable.

02

Remove the provider SDK from the client path

Keep the SDK and GEMINI_API_KEY in a module marked server-only. The browser should send its input to your own API and receive only the permitted generated output.

03

Protect the generation route before calling Gemini

Verify the user, validate input and request size, use a server-selected model and output cap, then reserve user and global quota atomically. Do not accept a model, key, or spend limit from the browser.

04

Deploy and prove the old key no longer works

Remove NEXT_PUBLIC provider variables, rebuild, and inspect fresh assets. Test the replacement route with a mock provider before making a small authorized provider call.

Implementation example

Next.js: keep Gemini configuration server-only

// lib/gemini-server.ts
import 'server-only';
export function getGeminiKey(): string {
  const key = process.env.GEMINI_API_KEY;
  if (!key) throw new Error('GEMINI_API_KEY is missing');
  return key;
}
// Only the authorized server handler imports this helper.
// Browser code sends input to /api/generate and never imports it.

Use the helper inside your installed Gemini SDK integration after authorization and quota reservation. It prevents accidental client imports; it does not implement authentication or quotas by itself.

Prove the repair

How to check that the fix worked

Run these checks with synthetic data in your test environment, then repeat the relevant acceptance checks after deployment.

  • New client assets: no provider key and no server helper.
  • Anonymous or over-quota request: denied before the mock provider call.
  • Old key: rejected by the provider after revocation.
  • Authorized bounded request: expected output, with usage recorded server-side.

If the check still fails

If costs continue after rotation, inspect other exposed keys and provider integrations. If the client still imports the SDK, follow the import chain from every use client entry point.

When the built-in AI fix makes it worse

Recover one reproducible failure.

Pause generated changes, restore a known working branch, and capture one failing request with its logs. Change one layer and rerun the allowed and denied checks before proceeding.

Engineering handoff

What Zenveus checks when the quick fix is not enough

We trace one production request through the complete path, isolate the failing boundary, and leave behind evidence your team can repeat.

Key delivery

Provider constructors and the served client graph that contains the credential.

Usage scope

Provider usage history, key restrictions, rotation, and old build artifacts.

Server gates

Authentication, atomic quotas, timeouts, and model/input/output bounds.

Denied-call evidence

Mock provider counts when identity, quota, or input checks fail.

Typical repair pattern

The key stays server-side and spending has a limit

No key appears in client assets; anonymous and over-quota requests stop before the provider call.

Evidence left behind
  • Root-cause note
  • Verified production check
  • Rollback and prevention steps

Before the next release

Prevent this failure from returning

Treat every public-prefixed provider credential as browser-visible.
Keep usage controls server-side even when the UI shows a credit balance.

Clear answers

Questions teams ask before they touch production

01Does a `NEXT_PUBLIC_` key stay secret in `.env`?

No. Next.js makes configured public-prefixed values available to browser code at build time.

02Will a frontend usage counter stop abuse?

It can guide the UI, but quota enforcement belongs at the server boundary before a billable call.

03Should the new server route be open to everyone?

A deliberate public demo can be anonymous with strict abuse controls. Account features should require identity and quotas.

04How do I know the fix worked in my app?

Run the verification checks on this page against your test environment, then repeat the relevant checks after deployment. Example code needs your app's authentication, data model, and configuration; reading the guide alone does not verify your deployment.

Official documentation and library references

Free next step

Check the boundary before you hand it over

The free tool helps you inspect this symptom. Its result does not establish whether the whole app is production ready.

The Verdict

Know whether the symptom is contained or structural.

We can see the symptom from here. What we cannot tell you from outside is whether it is contained or structural. A scanner collects evidence. A named senior engineer makes the decision. For $299, a named senior engineer reads your code and signs a written Verdict against the nine checks in the Zenveus Production Readiness Standard. The 48-hour clock begins when the required access and context are available. If the report does not give your developer a list they can act on, you do not pay.

The 48-hour clock starts when the required access and context are available.

Scroll to Top