Dotenv
Dotenv is the de facto standard, but it lacks validation, making it prone to errors. In this template, we use zod to validate the required environment variables in files like next.config.ts
. If any required variables are missing or invalid, the application will fail to execute. This approach ensures robustness at runtime.
sample
NODE_ENV=development
# Next.js
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Database
DATABASE_USER=dev
DATABASE_PASSWORD=1234
DATABASE_DB=dev
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_SCHEMA=public
# for prisma migration
DATABASE_URL=postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_DB}?schema=${DATABASE_SCHEMA}
# Google OAuth
# https://console.cloud.google.com/apis/credentials
# Set values below
# AUTHORIZED JAVASCRIPT ORIGINS: http://localhost:3000
# AUTHORIZED REDIRECT URIS: http://localhost:3000/api/auth/callback/google
GOOGLE_CLIENT_ID=xxxx
GOOGLE_CLIENT_SECRET=xxxx
# NextAuth.js
NEXTAUTH_URL=${NEXT_PUBLIC_SITE_URL}
# https://next-auth.js.org/configuration/options#secret
# you must generate a new secret
# error: "ikm" must be at least one byte in length'
# $ openssl rand -base64 32
NEXTAUTH_SECRET=TKDdLVjf7cTyTs5oWVpv04senu6fia4RGQbYHRQIR5Q=
# OpenTelemetry
TRACE_EXPORTER_URL=
ts
import dotenv from "dotenv";
import { z } from "zod";
export type Schema = z.infer<typeof schema>;
export const schema = z.object({
NODE_ENV: z
.union([
z.literal("development"),
z.literal("test"),
z.literal("production"),
])
.default("development"),
// for client and server
NEXT_PUBLIC_SITE_URL: z.string().url(),
// for server
DATABASE_USER: z.string().min(1),
DATABASE_PASSWORD: z.string().min(1),
DATABASE_DB: z.string().min(1),
DATABASE_HOST: z.string().min(1),
DATABASE_PORT: z.coerce.number().min(1),
DATABASE_SCHEMA: z.string().min(1),
DATABASE_URL: z.string().min(1),
GOOGLE_CLIENT_ID: z.string().min(1),
GOOGLE_CLIENT_SECRET: z.string().min(1),
NEXTAUTH_URL: z.string().min(1),
NEXTAUTH_SECRET: z.string().min(1),
TRACE_EXPORTER_URL: z.string().url().optional().or(z.literal("")),
});
export function config(file?: ".env" | ".env.test") {
if (file) {
dotenv.config({ path: file });
}
const res = schema.safeParse(process.env);
if (res.error) {
console.error("\x1b[31m%s\x1b[0m", "[Errors] environment variables");
console.error(JSON.stringify(res.error.errors, null, 2));
process.exit(1);
}
}
If you need to add new environment variables, make sure to add them to both .env
and .env.test
, and update the env.ts
with the corresponding zod validation.
Why not use .env.local?
Prisma can read the .env
file, and the DATABASE_URL
is a required key for migration. While Next.js prioritizes .env.local
, splitting the dotenv files can be inefficient. Therefore, to maintain consistency with Prisma, this template uses .env
.