Three things keep secrets out of the browser in Next.js. Environment variables stay on the server unless they start with NEXT_PUBLIC_. Importing the server-only package makes a build fail if a client file pulls the module in. React taint APIs block marked objects from being passed to the client.
The NEXT_PUBLIC_ rule
The Next.js docs state that environment variables are available only on the server by default, and that any variable prefixed with NEXT_PUBLIC_ is exposed to the client. The mistake is naming a real secret with that prefix because a client component needed it. Anything with the prefix should be treated as published.
server-only
Add one import at the top of a module that must never run in the browser:
import 'server-only'
If a client component imports that module, directly or indirectly, the build fails. The docs describe this as keeping proprietary code and internal business logic on the server. They also note Next.js handles the import itself, though you may install the package if your lint rules flag a missing dependency.
Taint APIs
React offers experimental_taintObjectReference for objects and experimental_taintUniqueValue for specific values. You enable them in next.config.js with experimental.taint set to true. A tainted object or value then cannot be passed to the client.
The guide is clear that this is an extra layer. You should still filter and sanitize data in your data access layer before it reaches React's render context. Because the API is experimental, check the current docs before relying on its shape.
What each protects against
- No NEXT_PUBLIC prefix: Secrets inlined into client bundles
- server-only: Server modules accidentally imported by client code
- Taint APIs: Private objects or values passed as props
- Narrow DTOs: Over-sharing in the first place
Related facts from the guide
- Server Components run only on the server and can access secrets and databases.
- Client Components also run on the server during prerendering, but must follow the same assumptions as browser code.
- Functions and classes are already blocked from being passed to Client Components by default.
A short checklist
- Search the repo for NEXT_PUBLIC_ and confirm each value is truly public.
- Add
server-onlyto database, auth and key handling modules. - Keep
process.envreads inside one server layer. - Review client component props for whole records.
Quick check
Can a NEXT_PUBLIC_ variable hold an API secret?
No. The prefix exposes the value to the client, so use it only for values that are fine to publish.
Is taint enough on its own?
The docs say it is an additional layer. Still filter data before passing it to the client.
Sources
Last reviewed: 19 October 2026.