Frontend API Keys and Secrets: What Is Safe to Put in NEXT_PUBLIC, VITE_, and Browser JavaScript? https://vulnify.app/blog/frontend-api-keys-secrets-next-public-vite-browser-javascript Understand which frontend keys are intentionally public, why NEXT_PUBLIC and VITE values are exposed, and how to keep privileged credentials on the server. Anything delivered to a browser must be treated as public to the user running that browser. That sounds obvious, yet frontend builds still leak private API keys because environment-variable names such as NEXT_PUBLIC_ or VITE_ look like configuration mechanisms rather than explicit publication mechanisms. Next.js documents that variables prefixed with NEXT_PUBLIC_ are bundled into JavaScript sent to the browser. Vite documents that variables using its default VITE_ prefix are exposed to client source code. These prefixes do not protect a secret. They mark a value for client-side exposure. Browser code cannot keep a secret A user controls their browser. They can inspect downloaded JavaScript, view network requests, set breakpoints, read source maps when available, modify runtime state, and call public APIs directly. Minification and bundling make code harder to read, not confidential. Obfuscation is not a security boundary. What NEXT_PUBLIC means In Next.js, a variable prefixed with NEXT_PUBLIC_ can be inlined into the browser bundle at build time. A value such as: NEXT_PUBLIC_ANALYTICS_ID=abc123 should therefore be something you are comfortable exposing to every visitor. An analytics site identifier may be designed for that. A database password is not. What VITE_ means Vite exposes variables prefixed with VITE_ to client-side code through import.meta.env . Its documentation explicitly warns that changing the environment prefix to an empty string can expose all environment variables. VITE_PUBLIC_MAP_ID=example DB_PASSWORD=do-not-expose The prefix is an exposure filter. It is not encryption or access control. Not every API key is a secret Some providers intentionally issue client-side identifiers or publishable keys. Browser mapping SDKs, analytics services, payment platforms, and CAPTCHA systems may use values that are expected to be visible. The correct question is not whether the value is called an API key. Ask what possession of the value allows. Can it authorize charges, read private data, change account settings, access storage, send email, or consume expensive services without another control? If yes, it does not belong in public browser code. Restrict intentionally public keys A publishable browser key should still be constrained when the provider supports it. Useful restrictions can include allowed web origins, referrer restrictions, API scope, application identifiers, quotas, and spending limits. Do not rely on origin restrictions as a substitute for server-side authorization. They reduce misuse but do not make the key secret. Move privileged calls server-side If an API requires a confidential credential, call it from your backend or a server-side function. The browser sends the user's intended action to your application; your server authenticates and authorizes the user, then makes the privileged downstream request with the secret. Browser -> POST /api/create-report Server -> verify user -> verify permission -> call third-party API with server-side secret -> return safe result The backend becomes the policy enforcement point rather than handing the credential to every user. Do not build an open secret proxy Moving a secret to the server is not enough if the new endpoint proxies arbitrary requests. Validate the action, destination, parameters, object ownership, and rate limits. A backend endpoint that accepts any URL and forwards it with your secret can create SSRF and credential abuse in one step. Source maps are not the root problem Source maps can make frontend code easier to understand, but removing them does not protect a secret that is already inside the JavaScript bundle. The browser still needs the value at runtime. Vulnify's existing article on production source-map security risk explains that source maps should be assessed for what they disclose, but secrets must be kept out of browser code regardless. Runtime frontend configuration is still public Some teams avoid build-time variables by loading a config.json file at runtime. That can be operationally useful, but it does not make the values private. If the browser downloads the file, users can read it. Use runtime configuration for public endpoints, feature flags that are safe to expose, analytics IDs, and similar values. Keep secrets on the server. Public endpoint URLs are not secrets The location of an API should not be treated as confidential. Protect it with authentication and authorization. Hiding an API hostname inside a bundle does not stop a client from discovering and calling it. Security by obscurity around endpoint names tends to fail because browsers necessarily reveal where they send requests. What if a secret was already shipped? Removing it from the next JavaScript build is not enough. Assume users or automated systems may have copied it. Revoke or rotate the exposed credential. Review provider logs for unexpected use. Restrict the replacement credential to the minimum scope. Remove it from source history where appropriate. Check build artifacts, old deployments, source maps, support tickets, and logs for copies. Move the privileged operation server-side. Repository cleanup is separate from runtime cleanup A credential can leak through both source control and deployed assets. Removing it from Git does not revoke the deployed copy, and removing it from the browser bundle does not erase repository history. Treat revocation as the first priority, then clean each distribution path. Inspect the deployed site, not only the repository Build systems can inject values during CI/CD that never exist in source control. Third-party tags can add their own keys. Old CDN assets can remain cached after the repository is fixed. Inspect the actual JavaScript and network requests delivered by production. Vulnify's Technology Fingerprint and other public tools can help with external context, while repository secret scanners remain important for source-level detection. Frontend secret review checklist Treat all browser-delivered values as public. Review every NEXT_PUBLIC_ and VITE_ variable. Classify keys by capability, not by name. Restrict intentionally public keys by origin, scope, quota, and spend where supported. Move privileged API calls server-side. Authenticate and authorize the server-side proxy endpoint. Rotate any credential that has already been exposed. Inspect deployed bundles and runtime configuration. Check CI/CD and repository history for old values. Use source maps only according to a deliberate exposure policy. Service workers and browser storage do not create secrecy Moving a key from a JavaScript constant into localStorage, IndexedDB, a service worker, or an encrypted blob shipped to the browser does not make it confidential from the user controlling that browser. If client code can recover the value to use it, a determined user can usually recover it too. Use browser storage for state that is appropriate to the browser security model, not to hide server credentials. Signed requests can help, but key placement still matters Some APIs use signed requests. A signature only proves possession of the signing key. If that private key is embedded in browser code, every user can extract it and generate their own signatures. Public clients need authentication mechanisms designed for public clients, such as OAuth flows with PKCE, rather than pretending they can hold a confidential shared secret. Differentiate public configuration from authorization Values such as an analytics property ID, public CAPTCHA site key, public payment publishable key, or API base URL can be intentionally visible. They should not grant access to privileged server operations on their own. Document this distinction in the repository. Naming a variable PUBLIC_ is useful only if developers know the downstream service also treats it as public. Public configuration can still reveal information Even non-secret frontend configuration can disclose unreleased features, internal environment names, or dormant routes. That may be acceptable, but classify it intentionally. Do not use client-side feature flags to protect a sensitive feature that the backend still exposes. The server must authorize hidden or unreleased functionality independently of whether the UI displays it. Secret scanning should cover build output as well as source Repository scanning can miss values injected only during CI/CD. Add checks against production build artifacts or deploy previews for patterns that resemble credentials. Keep false-positive handling disciplined so teams do not disable the scanner after repeated noisy findings. When a real secret is found, rotate first. Artifact cleanup is important, but a copied credential cannot be made safe again by deleting the file that exposed it. Public keys and identifiers still need lifecycle management A browser-visible key may not be confidential, but it can still be operationally important. Rotate it when vendors recommend rotation, remove keys for retired environments, and monitor quotas for misuse. If an origin restriction is available, keep the allowed origin list current. Do not call a publishable key a secret in documentation. Clear naming helps future developers avoid assuming that exposure is a vulnerability or, worse, putting a truly secret credential beside it. Do not publish internal server endpoints accidentally Even when a URL contains no credential, a frontend variable can expose an internal hostname, administrative service, or private integration path. Review public configuration for architecture details that the browser does not need. The strongest rule is still minimization: publish only what the client must know. The browser is the user's computer The simplest design rule is also the most reliable: if a value reaches browser JavaScript, assume the user can read and reuse it. Public identifiers belong there. Privileged credentials do not. Framework prefixes can help prevent accidental exposure, but only if developers understand that the prefix means publish.