
Next JS env Validation with Zod
Env validation for next js project, this will make sure all required env are provided in the .env file while also giving a type safety check
In schema.mjs
Copied to clipboardimport { z } from 'zod';
export const serverSchema = z.object({
  FOO: z.string(),
});
export const clientSchema = z.object({
  NEXT_PUBLIC_BAR: z.string(),
});
/**
 * @type {{ [k in keyof z.infer<typeof clientSchema>]: z.infer<typeof clientSchema>[k] | undefined }}
 */
export const clientEnv = {
  NEXT_PUBLIC_BAR: process.env.NEXT_PUBLIC_BAR,
};
In client.mjs
Copied to clipboardimport { clientEnv, clientSchema } from './schema.mjs';
const _clientEnv = clientSchema.safeParse(clientEnv);
export const formatErrors = (
  /** @type { import('zod').ZodFormattedError<Map<string, string>, string> } */
  errors,
) =>
  Object.entries(errors)
    .map(([name, value]) => {
      if (value && '_errors' in value) return ``NULL: ${value._errors.join(', ')}\n``;
    })
    .filter(Boolean);
if (_clientEnv.success === false) {
  console.error('❌ Invalid environment variables:\n', ...formatErrors(_clientEnv.error.format()));
  throw new Error('Invalid environment variables');
}
/**
 * Validate client-side env are exposed to the client
 */
for (const key of Object.keys(_clientEnv.data)) {
  if (!key.startsWith('NEXT_PUBLIC_')) {
    console.warn('❌ Invalid public environment variable name:\n', key);
    throw new Error('Invalid public environment variable name');
  }
}
export const env = _clientEnv.data;
In server.mjs
Copied to clipboardimport { serverSchema } from './schema.mjs';
import { env as clientEnv, formatErrors } from './client.mjs';
const _serverEnv = serverSchema.safeParse(process.env);
if (_serverEnv.success === false) {
  console.error('❌ Invalid environment variables:\n', ...formatErrors(_serverEnv.error.format()));
  throw new Error('Invalid environment variables');
}
/**
 * Validate server-side env are not exposed to the client
 */
for (const key of Object.keys(_serverEnv.data)) {
  if (key.startsWith('NEXT_PUBLIC_')) {
    console.warn('❌ You are exposing a server-side env:\n');
    throw new Error('You are exposing a server-side env');
  }
}
export const env = { ..._serverEnv.data, ...clientEnv };
In .env
FOO=FOO_VALUE
NEXT_PUBLIC_BAR=BAR_VALUE
In next.config.js
Copied to clipboardimport { env } from './server.mjs';
// ... rest of config
Usage
- 
In react component Copied to clipboardimport { env } from './client.mjs'; export const MyComponent = () => { return ( <div>{env.NEXT_PUBLIC_BAR}</div> ); };
- 
In api routes (where we don't expose the env value publicly) Copied to clipboardimport { env } from './server.mjs'; export const doSecretStuff = () => { console.log(``Secret: NULL``); };