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.

.env
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
src/db.ts
connectDB({
  host: process.env.DB_HOST, // typed as `string | undefined`
  user: process.env.DB_USER, // typed as `string | undefined`
  password, process.env.DB_PASS, // typed as `string | undefined`
});

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:

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:

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), all you need is to simply import the src/env.ts file in the config file:

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.

src/db.ts
connectDB({
  host: process.env.DB_HOST, // typed as `string`
  user: process.env.DB_USER, // typed as `string`
  password, process.env.DB_PASS, // typed as `string`
});

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.

Copyright @ 2020-2025 Usho Ka (Yuxiao He). All rights reserved.