Lesson CSS in Next.js explained why a styles.card class cannot collide with anyone else's.
CSS Modules compile it to a unique generated class name scoped to that file. Each module's classes are rewritten to unique generated names, so identical human-readable names in different files never clash in the compiled stylesheet.
This unit shifts from styling to shipping, and it opens with a rule that has the same flavour: a naming convention that decides what is visible where.
Secrets and settings
You met .env files and process.env in Backend with Node.js, and the rule carries over: config lives in environment variables, never in code. Next.js reads .env.local automatically:
DATABASE_URL=postgres://…
STRIPE_SECRET_KEY=sk_live_…
NEXT_PUBLIC_ANALYTICS_ID=abc123Here is the twist that makes Next.js different from a plain Node server. A Next.js app runs in two places at once, some code on the server and some in the browser, and a variable readable by browser code is readable by every visitor, not just by you. So Next.js needs to know, per variable, which side may see it, and it decides by a naming rule:
- Plain names such as
DATABASE_URLare server-only. Server components, actions, and route handlers can read them. They never reach the browser. - Names starting with
NEXT_PUBLIC_are inlined into the browser bundle at build time. Anyone can read them in developer tools, so only genuinely public values belong here.
publicEnvNames: the exposure audit
This is the audit worth running before any deploy, expressed as plain JavaScript. publicEnvNames(names) takes an array of environment variable names and returns only the ones Next.js will inline into the browser bundle, meaning the ones starting with "NEXT_PUBLIC_".
If a secret ever shows up in that list, it is about to be readable by every visitor to your site.
function publicEnvNames(names) { return names.filter((n) => n.startsWith("NEXT_PUBLIC_")); } const names = [ "DATABASE_URL", "NEXT_PUBLIC_ANALYTICS_ID", "STRIPE_SECRET_KEY", "NEXT_PUBLIC_APP_NAME", ]; for (const n of publicEnvNames(names)) console.log(n);
Output
NEXT_PUBLIC_ANALYTICS_ID NEXT_PUBLIC_APP_NAME
Reading the check
Array.prototype.filterkeeps only the elements for which the callback returns true, so the result is the public subset.String.prototype.startsWith("NEXT_PUBLIC_")is the exact test Next.js itself performs during the build. There is no allowlist and no cleverness beyond the prefix.DATABASE_URLandSTRIPE_SECRET_KEYare absent from the output, which is what you want to see. An audit that surfaces a secret is telling you the variable was misnamed.
Why a client component sees undefined
Here is a situation that confuses people the first time. A client component reads process.env.STRIPE_SECRET_KEY and gets undefined, while a server action reads the same variable without any trouble.
This is deliberate, not a bug. Only NEXT_PUBLIC_* variables are compiled into client code. Every other variable stays server-side so a secret key can never leak into the bundle that visitors download. From the browser's point of view the variable simply does not exist, and undefined is the honest answer.
The fix is to keep Stripe calls in server code, where the key is available. Renaming the variable would "work" in the narrow sense of making the value appear, which is exactly the trap the next block takes apart.
The rename that publishes your secret key
Renaming STRIPE_SECRET_KEY to NEXT_PUBLIC_STRIPE_SECRET_KEY is never a safe fix for an undefined value in a client component. It is dangerous.
The NEXT_PUBLIC_ prefix inlines the value into the JavaScript sent to every visitor, which publishes your secret key to the world. Anyone can open their browser's developer tools, search the bundle, and find it. With a live Stripe secret key, that means anyone can issue charges and refunds against your account.
The correct fix is architectural: perform the Stripe call in server code, either a server action or a route handler, and keep the plain server-only variable name. The client component then calls your server code and never needs the key at all.
| Variable name | Readable by server code | Readable by browser code | Use it for |
|---|---|---|---|
DATABASE_URL | Yes | No | Secrets, connection strings, API keys |
NEXT_PUBLIC_ANALYTICS_ID | Yes | Yes | Values that are safe on a billboard |
A useful test before adding the prefix: would you be comfortable printing this value on a billboard. If not, it must not carry
NEXT_PUBLIC_.