Environment Variables
Environment variables are used to configure the project depending on the environment without hardcoding the values. In this page, we will go through briefly about how to use environment variables in Next.js. Then we will discuss the best practices to use environment variables in a Next.js app.
Next.js
It is pretty common to store sensitive information in environment variables. So to prevent accidental exposure of sensitive information, there a few important rules to follow in a Next.js app:
Variables without NEXT_PUBLIC_ prefix
Variables without the NEXT_PUBLIC_ prefix are only readable in the server. They will not be included in the JavaScript bundle that is sent to the client.
Warning
Every credential that is used to access resources in the backend must not have
the NEXT_PUBLIC_ prefix, e.g, database password, API keys, API tokens, etc.
The two servers
React component was initially developed to be running in the browser. But since version 18, React has introduced Server Components, which are components that are executed on the server.
Two important things to note in React and Next.js:
- "server" environment means two things: the environment during build time (
next build) and the environment during runtime (next start). - A React Server Component (RSC) in Next.js can be either a static component or a dynamic component.
A static RSC is executed during build time and rendered as static HTML. In Next.js, an RSC becomes static when it does not call any dynamic functions, e.g., cookies, headers, connection, etc. Once built, it is no longer executed during runtime. In result, changing the value of a non NEXT_PUBLIC_ environment variable after the build will not change the component's output.
On the other hand, a dynamic RSC is executed during runtime. Changing the value of a non NEXT_PUBLIC_ environment variable during runtime will change the component's output.
Good to know
If you change the value of the environment variables, you need to restart the app to see the changes in the dynamic RSC.
Variables with NEXT_PUBLIC_ prefix
Variables with the NEXT_PUBLIC_ prefix are readable in both the server and the client. During build time, the value of the variable will be included in the JavaScript bundle that is sent to the client. Just like static RSC, changing the value of a NEXT_PUBLIC_ environment variable after the build will not change the component's output.
Warning
The value of a NEXT_PUBLIC_ environment variable can be read by anyone who
opens the page in the browser even if the variable is not rendered in the
page. It can be seen by anyone who inspects the page source code. So be
careful not to include sensitive information in a NEXT_PUBLIC_ environment
variable.
Documentation
One thing that developers often overlook is the documentation of the environment variables. It is important to document the purpose of the environment variables, the possible values, and the default values.
Every project must have a env.example file that contains the example values of the environment variables. This file should be committed to the repository so that the developers can easily see the possible values of the environment variables. And on each variable, a comment should be added to explain the purpose of the variable, where to get the value from, and any other relevant information.
# Comma separated app's name to enable debugging. Leave empty if not needed
DEBUG=web,facebook-webhooks
# For docker postgres initialization
POSTGRES_USER=root
POSTGRES_PASSWORD=root
# The URL of the Postgres database pool to store user data
DATABASE_URL="postgresql://postgres:root@localhost:5432/postgres?schema=public" #required
# The URL of the direct Postgres database. Used for Prisma migrations
DATABASE_DIRECT_URL="postgresql://postgres:root@localhost:5432/postgres"
# The access token to use to access the Facebook Whatsapp API. You can get the access token from `https://developers.facebook.com/apps/{app_id}/whatsapp-business/wa-dev-console/?business_id={whatsapp_business_account_id}`. Replace `{app_id}` with the app id and `{whatsapp_business_account_id}` with the whatsapp business account id.
WHATSAPP_API_ACCESS_TOKEN=abcd #requiredType safety
By default, reading a variable from process.env will return a string or undefined. This causes a lot of if-else dance in the code. Or worse, some people just force cast the value to a string without checking if it is undefined.
The community has come up with a few solutions to this problem. One of the most popular ones is to use T3 Env. While it's helpful, it gets cumbersome the more variables you have. But this can be solved using env-to-t3. Using this tool, you can generate a type-safe environment variables object from your environment variable example file.
For example, if you have the following env.example file:
# The URL of the Postgres database pool to store user data
DB_URL=postgresql://postgres:root@localhost:5432/postgres #required
# The domain of the app
NEXT_PUBLIC_DOMAIN=https://my-app.com #required
# The timeout in milliseconds for the fetch request
NEXT_PUBLIC_FETCH_TIMEOUT_MS=10000 #number #defaultThen we run the following command to generate the type-safe environment variables object:
npx env-to-t3 -i env.example --output src/env.tsThis will generate the following src/env.ts file:
// This file is automatically generated by [env-to-t3 CLI](https://github.com/nicnocquee/env-to-t3)
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
DB_URL: z.string().min(1),
},
client: {
NEXT_PUBLIC_DOMAIN: z.string().min(1),
NEXT_PUBLIC_FETCH_TIMEOUT_MS: z
.number({ coerce: true })
.default(10000)
.optional(),
},
experimental__runtimeEnv: {
NEXT_PUBLIC_DOMAIN: process.env.NEXT_PUBLIC_DOMAIN,
NEXT_PUBLIC_FETCH_TIMEOUT_MS: process.env.NEXT_PUBLIC_FETCH_TIMEOUT_MS,
},
});Then you can use the environment variables in the code like this:
import { env } from "@/env";
export async function GET(request: Request) {
const dbUrl = env.DB_URL; // string
const fetchTimeoutMs = env.NEXT_PUBLIC_FETCH_TIMEOUT_MS; // number
}Env Package
In this monorepo, we have a shared package called @workspace/env in the packages/env directory that contains the type-safe environment variables object. This package is used to share the environment variables between the apps and packages.
Building the package
The TypeScript code generated by env-to-t3 is stored in the src/index.ts file. This file is automatically generated by the build script in the package.json file. You have to run pnpm build to generate the type-safe environment variables object whenever you update the env.example file.
Using the package
To use the environment variables in the code in an app, you need to add the @workspace/env package as a dependency in the package.json file.
{
"dependencies": {
"@workspace/env": "workspace:*"
}
}Then you can import the environment variables object in the code like this:
import { env } from "@workspace/env";
export async function GET(request: Request) {
const dbUrl = env.DB_URL; // string
const fetchTimeoutMs = env.NEXT_PUBLIC_FETCH_TIMEOUT_MS; // number
}Sharing .env file
During the development, you might need to use the same environment variables between the apps and packages. To do this, you can create a .env file in the packages/env directory and run the dev-bootstrap.sh script from the root of the project to create symlinks from the .env file in the packages/env directory to the .env.local file in the apps directory.
Ground Rules
| Action | |
|---|---|
| ✅ | Add the environment variables to the env.example file. |
| ✅ | Add comments to the environment variables in the env.example file. |
| ✅ | Add annotation to the environment variables in the env.example file so that env-to-t3 can generate the type-safe environment variables object. |
| ✅ | Never call process.env directly in the code. Import the environment variables object from the @workspace/env package instead. |
| ✅ | Secrets, credentials, and other sensitive information must not have the NEXT_PUBLIC_ prefix. |
Todos
- Create or find a linter rule to prevent directly reading from
process.envin the code. - Update the
env-to-t3tool to generate the type-safe environment variables object for the Next.js app.