
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``); };