Type-Safe Env Vars in Next.js
The Problem
When working with Next.js, you can set up environment variables using .env files and access them through process.env.
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypasswordconnectDB({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password, process.env.DB_PASS,
});While this is convenient, it comes with a TypeScript limitation: any environment variable you access will be typed as string | undefined. This means you won't get proper autocompletion in your IDE and making typos won’t generate any type error.
Making Env Vars Type-Safe
Matt Pocock suggested a smart solution using Zod to add type safety to your process.env:
import { z } from "zod";
const env = z.object({
DB_HOST: z.string(),
DB_USER: z.string(),
DB_PASS: z.string(),
});
const result = env.safeParse(process.env);
if (!result.success) {
console.error(
'Error happened when parsing process.env:',
JSON.stringify(result.error.errors, null, 2),
);
throw new Error('process.env parsing error');
}
declare global {
namespace NodeJS {
interface ProcessEnv extends z.infer<typeof env> {}
}
}Once implemented, your IDE will provide proper autocompletion and process.env.DB_HOST will be correctly typed as a string.
Validating process.env at Build Time
Then we need to execute validation logic (env.parse(process.env)) to actually parse process.env. Doing it at build time is a great option.
The T3 Env package offers an excellent approach using the jiti library to validate the env vars in the next.config.js file:
import { fileURLToPath } from "node:url";
import createJiti from "jiti";
const jiti = createJiti(fileURLToPath(import.meta.url));
jiti("./src/env");
/** @type {import('next').NextConfig} */
export default {
/* ... */
};This means if there's any problem with your environment variables, the build will fail.
If you are using TypeScript for the Next config (next.config.ts), you can directly import the src/env.ts file in the config file:
import type { NextConfig } from 'next';
import "./src/env";
const nextConfig: NextConfig = {
/* ... */
};
export default nextConfig;That's it! With this setup, your process.env is now fully type-safe.
For even more functionality, check out the T3 Env package. But note that it takes a different approach where you import environment variables from a module rather than using process.env directly.