next-forge uses @t3-oss/env-nextjs to handle environment variables in a type-safe way. This provides runtime validation and autocompletion for all environment variables.

Overview

Environment variables are defined and validated in packages/env/index.ts. The file exports an env object that contains all validated environment variables, separated into server and client variables.

Initial setup

App Environment Variables

As part of the initial setup, you will need to fill in the environment variables in each .env.local file in each Next.js application, specifically app, web and api.

The initial setup script will copy these .env.example files to .env.local files, so all you need to do is fill in the variables.

Check out the packages/env/index.ts file to see the validation rules for each variable.

For FLAGS_SECRET, you can run node -e "console.log(crypto.randomBytes(32).toString('base64url'))" or openssl rand -base64 32 in your terminal to generate a random value.

Some environment variables will be added by integrations and other tooling. For example, environment variables prefixed with SENTRY_ are automatically added to a Vercel project when you add the Sentry integration from the Vercel Marketplace. Additionally, NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL is a very handy environment variable that refers to the “production” URL to which this project is deployed on Vercel.

Database Environment Variable

You also need to configure a .env file in the packages/database directory. This file should be created by the setup script, so just add your DATABASE_URL and you should be good to go.

Adding a variable

To add a new environment variable, you need to do two things:

  1. Add the variable to each of the .env.local files across the repo
  2. Add the variable to the server or client object in packages/env/index.ts. The variable will be validated using Zod, so you can use any of the Zod validation primitives.

I recommend being as specific as possible with your validation. For example, if you know that a vendor secret starts with sec_, you should validate it as z.string().min(1).startsWith('sec_'). This will not only make your intent clearer to anyone reading your code, but will also help prevent errors at runtime.