Safeguard
Developer Tools•

Next.js NEXT_PUBLIC_ variables are public: what gets inlined

Any Next.js variable prefixed NEXT_PUBLIC_ is copied into the browser bundle at build time. Here is what that means for secrets and for config changes.

Safeguard Security Team
4 min read

A Next.js environment variable with the NEXT_PUBLIC_ prefix is copied into the JavaScript bundle at build time and shipped to every browser. Anything without the prefix stays on the server. So never put a secret behind that prefix, and remember the value is frozen when you run the build.

What the prefix does

The Next.js documentation says variables without the NEXT_PUBLIC_ prefix are only available in the Node.js environment. The browser runs somewhere else and cannot see them.

To make a value reachable in the browser, Next.js inlines it. During the build, it replaces every reference to the variable with the literal value from the environment where next build ran. The result is plain text inside files your users download.

That is fine for an analytics site ID or a public map key. It is a leak for a database password, a payment secret key or a signing key.

Three ways this goes wrong

First, a developer renames a variable to get it working in a client component. The code starts working, and a secret is now public. The error message only said the variable was undefined, so the fix looks harmless.

Second, a value is copied from a server file into a shared config module that both server and client code import. The prefix is added to satisfy a lint rule or a runtime error.

Third, the team assumes the value can be rotated by changing an environment setting. It cannot, because the docs state that after the build the app no longer responds to changes in these variables.

The build-time freeze is also a config problem

The same docs describe a pipeline that builds one Docker image and deploys it to several environments. In that setup every NEXT_PUBLIC_ variable keeps the value from build time. The staging image promoted to production still points at staging endpoints unless you rebuild.

If you need runtime values in the browser, the documentation suggests your own API that serves them to the client, either on demand or at initialization. For server-side code, reading variables during dynamic rendering lets one image move between environments with different values.

A review rule that catches it

Add one check to code review and CI: every new or changed NEXT_PUBLIC_ name gets a question. Would we be comfortable printing this value on our homepage? If the answer is no, the prefix is wrong.

You can automate the first pass. List the prefixed names in the repository and compare them with an allowlist. Anything new fails the pull request until a person approves it. The allowlist file also becomes a short, readable inventory of what your public bundle contains.

After a build, you can also search the output folder for strings that look like credentials. This catches the case where someone hardcoded a value instead of using a variable.

If a secret already shipped

Treat it as exposed from the first deploy that contained it. Rotate the credential first. Then fix the code and redeploy. Removing the prefix without rotating leaves the old value valid and cached in browsers, CDNs and any archive of your bundle.

Quick check

Is a NEXT_PUBLIC_ variable ever safe for a secret?

No. The value is inlined into the bundle sent to browsers, so anyone can read it. Use the prefix only for values you would publish.

Can I change a NEXT_PUBLIC_ value after the build?

Not in the built app. The docs say these values are frozen at build time, so a change needs a rebuild, or a runtime API that serves the value.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.