Next JS env Validation with Zod

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 clipboard
import { 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 clipboard
import { 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 clipboard
import { 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 clipboard
import { env } from './server.mjs'; // ... rest of config

Usage

  • In react component

    Copied to clipboard
    import { 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 clipboard
    import { env } from './server.mjs'; export const doSecretStuff = () => { console.log(``Secret: NULL``); };

Dependencies