TidyURLs
Go to Tools
BLOG
The TidyURLsJournal

Practical guides, tutorials, and insights about URLs, SEO, web development, and online marketing. Learn, troubleshoot, and get the most of the web.

Back to the Journal
TidyURLs Journal

How to Store Environment Variables and Secrets Safely in a Next.js Project

Practical insights, clear explanations, and useful ideas for understanding the web.

October 25, 20266 min read5 topics
How to Store Environment Variables and Secrets Safely in a Next.js Project
Explore the details
#Next.js#environment variables#secrets management#web security#deployment security
October 25, 20266 min read

Environment variables make it convenient to configure a Next.js app, but convenience can create security risks: a private key can end up in a browser bundle, a local file can be committed to Git, or a leaked credential can remain active after the file is deleted. This guide explains how to keep secrets on the server, handle local and deployment configuration carefully, and respond if a value is exposed.

Key takeaways

  • Treat every variable prefixed with NEXT_PUBLIC_ as public. Next.js can inline its value into browser JavaScript during the build.
  • Keep private values in server-side code, and never pass them to client components, browser responses, or logs.
  • A variable name does not make a value secret. The code and build configuration determine where it can go.
  • Keep local environment files out of Git, and set production values through your hosting provider's protected configuration.
  • If a secret is exposed, revoke or rotate it. Deleting the file or Git commit alone does not make the old credential safe.

How Next.js environment variables work

Next.js can load environment variables from .env files in the project root and from the process environment. Common filenames include .env.local, .env.development, and .env.production. The exact files used depend on the environment and Next.js conventions, so check the documentation for your installed version when relying on load order or special cases.

A variable can be read on the server through process.env:

const serviceToken = process.env.SERVICE_TOKEN;

That statement alone does not guarantee the value stays private. A server can expose it by returning it in an API response, passing it as a prop to client-side code, writing it to logs, or including it in a generated page. Review the path the value takes, not just where it is defined.

What NEXT_PUBLIC_ means

The NEXT_PUBLIC_ prefix is for values intended to be available in browser code, such as a public analytics identifier. Next.js replaces statically referenced public variables with their values during the build. As a result, a public value may be visible in downloaded JavaScript, and changing the environment after a build may not change the value already embedded in that build.

Do not use this prefix for passwords, API tokens, private keys, signing secrets, or any other credential. A prefix is not a secrecy control: it marks a value for client exposure.

Keep secrets in server-only code

Use secret variables only in server-side code that needs them. For example, a server-side service module can read a credential to contact a provider, then return only the necessary result to the client.

Where appropriate, mark a module as server-only so Next.js can help catch accidental imports into client-side code:

import "server-only";

export function getServiceToken(): string {
  const token = process.env.SERVICE_TOKEN;

  if (!token) {
    throw new Error("SERVICE_TOKEN is not configured");
  }

  return token;
}

This is a useful boundary check, not a complete secret-protection system. Keep secrets out of client component props, serialized page data, browser-visible error messages, and responses sent to users. Also review next.config.js or next.config.ts: configuration that explicitly exposes values to client code can undermine otherwise careful handling.

Manage local .env files safely

For local development, put private values in an appropriate local environment file, commonly .env.local. Use a separate .env.example to document the variable names the project requires, without putting actual credentials in it:

# .env.example — names only; no credentials
DATABASE_URL=
SERVICE_TOKEN=
NEXT_PUBLIC_ANALYTICS_ID=

Check the project's .gitignore rather than assuming these files are excluded. Many Next.js project templates ignore environment files, but repository rules can be changed, overridden, or incomplete. You can check whether a file is ignored with:

git check-ignore -v .env.local

Before committing, inspect staged files with git status and review the diff. Avoid copying production secrets into local files, chat messages, issue trackers, or shared documents. Give developers separate credentials with limited permissions when possible.

Configure deployment variables deliberately

Set production secrets in your hosting provider's environment-variable or secret-management settings, not in source code. Scope each value to the environments that need it, such as preview, staging, or production, and restrict who can view or change those settings.

Be mindful of build-time versus runtime behavior. Public NEXT_PUBLIC_ values are embedded during the build when statically referenced. Server-side values may be read during a build or at runtime depending on how and when a route or page is rendered and how the app is deployed. Confirm the behavior for your Next.js version and hosting platform, and provide any build-time values before the relevant build step. Do not assume that changing a dashboard setting updates an already-built client bundle.

Keep secrets out of deployment logs and diagnostic output. If a build step prints environment variables or errors containing credentials, restrict access to the logs and remove or mask the unsafe output. Prefer short-lived or narrowly scoped credentials where the service supports them.

A practical review workflow

  1. Classify each variable. Decide whether it must be secret, is intentionally public, or is ordinary configuration. Do not label a credential public just to make it accessible in browser code.
  2. Find every use. Search the codebase for the variable name and check server modules, client components, API responses, page data, logging, and build configuration.
  3. Keep local files untracked. Confirm the relevant .env files are ignored and make sure .env.example contains names or safe descriptions only.
  4. Configure each deployment environment. Add only the required values to the correct hosting environment and limit access to those settings.
  5. Check build and runtime behavior. Verify when the app reads each value, especially for static rendering and public values embedded at build time.
  6. Review changes before release. Inspect staged files, build output, and logs for accidental disclosure. Use test credentials in development rather than production credentials.

If a secret is committed or exposed

Treat an exposed credential as compromised, even if the repository is private or the file was removed quickly.

  1. Revoke the credential or rotate it with the provider. Update the application and redeploy using the replacement value.
  2. Check for unauthorized use through the provider's available audit or usage records. Follow the provider's incident process if misuse is suspected.
  3. Remove the value from the current code and prevent further commits. If needed, follow your Git host's guidance for removing sensitive data from repository history; history cleanup does not replace rotation.
  4. Review related secrets and access permissions. Rotate other credentials if they were also exposed or could have been accessed through the compromised account.
  5. Fix the cause, such as an incomplete ignore rule, unsafe logging, or a secret passed into client-side code.

Common mistakes

  • Assuming an unprefixed name is automatically safe. Names such as SECRET_KEY do not enforce server-only access. Check where the value is referenced and whether it is exposed.
  • Using NEXT_PUBLIC_ for a server credential. Anything intended to remain confidential should not use this prefix.
  • Deleting a leaked value without rotating it. A copied credential can remain usable after its file or commit is removed.
  • Relying only on .gitignore. Ignore rules help prevent new additions; they do not remove a file already tracked by Git. Check the repository's tracked files and staged changes.
  • Assuming every environment change takes effect immediately. Public values can be embedded at build time, and build-time rendering can consume server variables before deployment.
  • Printing configuration to debug a problem. Logging all environment variables can disclose credentials to anyone with access to logs.

Conclusion

Secure Next.js configuration depends on controlling where each value is read and where its data can flow. Keep private credentials in server-side code, treat NEXT_PUBLIC_ values as browser-visible, verify Git and deployment settings, and rotate any secret that may have been exposed. These practices reduce accidental disclosure, but they do not replace appropriate access controls and incident-response procedures.