Skip to main content
TypeScriptNext.js

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.

plain text
# .env
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
typescript
// src/db.ts
connectDB({
  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:

typescript
// src/env.ts
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:

javascript
// next.config.js
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:

typescript
// next.config.ts
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.