Typesafe environment variable validation and management for Next.js applications with runtime compatibility enforcement.
npx @tessl/cli install tessl/npm-t3-oss--env-nextjs@0.13.0@t3-oss/env-nextjs provides typesafe environment variable validation and management specifically designed for Next.js applications. This package automatically enforces Next.js naming conventions (NEXT_PUBLIC_ prefix for client variables), ensures compatibility across all Next.js runtime environments (Node.js server, Edge Runtime, and browser client), and provides compile-time type checking with runtime validation using Standard Schema-compliant validators.
npm install @t3-oss/env-nextjsimport { createEnv } from "@t3-oss/env-nextjs";For CommonJS:
const { createEnv } = require("@t3-oss/env-nextjs");import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
// Server-side environment variables (not available on client)
server: {
DATABASE_URL: z.string().url(),
OPEN_AI_API_KEY: z.string().min(1),
},
// Client-side environment variables (requires NEXT_PUBLIC_ prefix)
client: {
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
},
// Shared environment variables (available on both server and client)
shared: {
NODE_ENV: z.enum(["development", "test", "production"]),
},
// Runtime environment values
runtimeEnv: {
DATABASE_URL: process.env.DATABASE_URL,
OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
NODE_ENV: process.env.NODE_ENV,
},
});
// Access environment variables with full type safety
console.log(env.DATABASE_URL); // string
console.log(env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY); // string
console.log(env.NODE_ENV); // "development" | "test" | "production"@t3-oss/env-nextjs is built around several key components:
createEnv function that creates typesafe environment variable schemas with Next.js-specific configurationsNEXT_PUBLIC_ prefixruntimeEnv and experimental experimental__runtimeEnv modesCore functionality for creating typesafe environment variable schemas with Next.js-specific validation rules and runtime compatibility enforcement.
function createEnv<
TServer extends StandardSchemaDictionary = NonNullable<unknown>,
TClient extends Record<`NEXT_PUBLIC_${string}`, StandardSchemaV1> = NonNullable<unknown>,
TShared extends StandardSchemaDictionary = NonNullable<unknown>,
const TExtends extends Array<Record<string, unknown>> = [],
TFinalSchema extends StandardSchemaV1<{}, {}> = DefaultCombinedSchema<TServer, TClient, TShared>
>(opts: Options<TServer, TClient, TShared, TExtends, TFinalSchema>): CreateEnv<TFinalSchema, TExtends>;
type Options<
TServer extends StandardSchemaDictionary,
TClient extends Record<`NEXT_PUBLIC_${string}`, StandardSchemaV1>,
TShared extends StandardSchemaDictionary,
TExtends extends Array<Record<string, unknown>>,
TFinalSchema extends StandardSchemaV1<{}, {}>
> = Omit<
StrictOptions<"NEXT_PUBLIC_", TServer, TClient, TShared, TExtends> &
ServerClientOptions<"NEXT_PUBLIC_", TServer, TClient> &
CreateSchemaOptions<TServer, TClient, TShared, TFinalSchema>,
"runtimeEnvStrict" | "runtimeEnv" | "clientPrefix"
> & (
| {
runtimeEnv: StrictOptions<"NEXT_PUBLIC_", TServer, TClient, TShared, TExtends>["runtimeEnvStrict"];
experimental__runtimeEnv?: never;
}
| {
runtimeEnv?: never;
experimental__runtimeEnv: Record<
| {
[TKey in keyof TClient]: TKey extends `NEXT_PUBLIC_${string}` ? TKey : never;
}[keyof TClient]
| {
[TKey in keyof TShared]: TKey extends string ? TKey : never;
}[keyof TShared],
string | boolean | number | undefined
>;
}
);Pre-configured environment variable schemas for popular hosting platforms and services using Zod validators. Includes presets for Vercel, Railway, Render, Netlify, and many other platforms.
// Example preset functions
function vercel(): Readonly<VercelEnv>;
function railway(): Readonly<RailwayEnv>;
function render(): Readonly<RenderEnv>;
function netlify(): Readonly<NetlifyEnv>;Pre-configured environment variable schemas using Arktype validators. Provides the same platform presets as Zod but using Arktype's type system for validation.
// Example preset functions
function vercel(): Readonly<VercelEnv>;
function railway(): Readonly<RailwayEnv>;
function render(): Readonly<RenderEnv>;
function netlify(): Readonly<NetlifyEnv>;Pre-configured environment variable schemas using Valibot validators. Provides the same platform presets as Zod and Arktype but using Valibot's validation system.
// Example preset functions
function vercel(): Readonly<VercelEnv>;
function railway(): Readonly<RailwayEnv>;
function render(): Readonly<RenderEnv>;
function netlify(): Readonly<NetlifyEnv>;// Client prefix type
type ClientPrefix = "NEXT_PUBLIC_";
// Standard schema types (from @t3-oss/env-core)
interface StandardSchemaDictionary {
[key: string]: StandardSchemaV1;
}
interface StandardSchemaV1<Input = any, Output = Input> {
readonly "~standard": {
readonly version: 1;
readonly vendor: string;
readonly validate: (value: unknown) => StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>>;
readonly types?: StandardSchemaV1.Types<Input, Output> | undefined;
};
}
declare namespace StandardSchemaV1 {
// Standard Schema result types
type Result<Output> = SuccessResult<Output> | FailureResult;
interface SuccessResult<Output> {
readonly value: Output;
readonly issues?: undefined;
}
interface FailureResult {
readonly issues: ReadonlyArray<Issue>;
}
// Issue interface for validation errors
interface Issue {
readonly message: string;
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
}
interface PathSegment {
readonly key: PropertyKey;
}
interface Types<Input = unknown, Output = Input> {
readonly input: Input;
readonly output: Output;
}
}
// Base configuration options
interface BaseOptions<
TShared extends StandardSchemaDictionary,
TExtends extends Array<Record<string, unknown>>
> {
/** How to determine whether the app is running on the server or the client */
isServer?: boolean;
/** Shared variables available on both client and server */
shared?: TShared;
/** Extend presets */
extends?: TExtends;
/** Called when validation fails */
onValidationError?: (issues: readonly StandardSchemaV1.Issue[]) => never;
/** Called when a server-side environment variable is accessed on the client */
onInvalidAccess?: (variable: string) => never;
/** Whether to skip validation of environment variables */
skipValidation?: boolean;
/** Treat empty strings as undefined for better default value handling */
emptyStringAsUndefined?: boolean;
}
// Loose runtime environment options
interface LooseOptions<
TShared extends StandardSchemaDictionary,
TExtends extends Array<Record<string, unknown>>
> extends BaseOptions<TShared, TExtends> {
runtimeEnvStrict?: never;
/** Runtime environment object - usually process.env */
runtimeEnv: Record<string, string | boolean | number | undefined>;
}
// Strict runtime environment options
interface StrictOptions<
TPrefix extends string | undefined,
TServer extends StandardSchemaDictionary,
TClient extends StandardSchemaDictionary,
TShared extends StandardSchemaDictionary,
TExtends extends Array<Record<string, unknown>>
> extends BaseOptions<TShared, TExtends> {
/** Strict runtime environment that enforces all variables are specified */
runtimeEnvStrict: Record<string, string | boolean | number | undefined>;
runtimeEnv?: never;
}
// Client-side configuration options
interface ClientOptions<
TPrefix extends string | undefined,
TClient extends StandardSchemaDictionary
> {
/** Prefix for client-side variables (e.g., NEXT_PUBLIC_) */
clientPrefix: TPrefix;
/** Client-side environment variable schemas */
client: Partial<Record<keyof TClient, TClient[keyof TClient]>>;
}
// Server-side configuration options
interface ServerOptions<
TPrefix extends string | undefined,
TServer extends StandardSchemaDictionary
> {
/** Server-side environment variable schemas */
server: Partial<Record<keyof TServer, TServer[keyof TServer]>>;
}
// Schema creation options
interface CreateSchemaOptions<
TServer extends StandardSchemaDictionary,
TClient extends StandardSchemaDictionary,
TShared extends StandardSchemaDictionary,
TFinalSchema extends StandardSchemaV1<{}, {}>
> {
/** Custom function to combine the schemas */
createFinalSchema?: (
shape: TServer & TClient & TShared,
isServer: boolean
) => TFinalSchema;
}
// Combined server/client options
type ServerClientOptions<
TPrefix extends string | undefined,
TServer extends StandardSchemaDictionary,
TClient extends StandardSchemaDictionary
> =
| (ClientOptions<TPrefix, TClient> & ServerOptions<TPrefix, TServer>)
| (ServerOptions<TPrefix, TServer> & Impossible<ClientOptions<never, never>>)
| (ClientOptions<TPrefix, TClient> & Impossible<ServerOptions<never, never>>);
// Environment creation options
type EnvOptions<
TPrefix extends string | undefined,
TServer extends StandardSchemaDictionary,
TClient extends StandardSchemaDictionary,
TShared extends StandardSchemaDictionary,
TExtends extends Array<Record<string, unknown>>,
TFinalSchema extends StandardSchemaV1<{}, {}>
> = (
| (LooseOptions<TShared, TExtends> & ServerClientOptions<TPrefix, TServer, TClient>)
| (StrictOptions<TPrefix, TServer, TClient, TShared, TExtends> & ServerClientOptions<TPrefix, TServer, TClient>)
) & CreateSchemaOptions<TServer, TClient, TShared, TFinalSchema>;
// Core creation types
type CreateEnv<
TFinalSchema extends StandardSchemaV1<{}, {}>,
TExtends extends Array<Record<string, unknown>>
> = Readonly<Simplify<Reduce<[StandardSchemaV1.InferOutput<TFinalSchema>, ...TExtends]>>>;
type DefaultCombinedSchema<
TServer extends StandardSchemaDictionary,
TClient extends StandardSchemaDictionary,
TShared extends StandardSchemaDictionary
> = StandardSchemaV1<
{},
UndefinedOptional<StandardSchemaDictionary.InferOutput<TServer & TClient & TShared>>
>;
// Utility types
type ErrorMessage<T extends string> = T;
type Simplify<T> = { [P in keyof T]: T[P] } & {};
type UndefinedOptional<T> = Partial<Pick<T, PossiblyUndefinedKeys<T>>> & Omit<T, PossiblyUndefinedKeys<T>>;
type PossiblyUndefinedKeys<T> = {
[K in keyof T]: undefined extends T[K] ? K : never;
}[keyof T];
type Impossible<T extends Record<string, any>> = Partial<Record<keyof T, never>>;
type Reduce<
TArr extends Record<string, unknown>[],
TAcc = object
> = TArr extends []
? TAcc
: TArr extends [infer Head, ...infer Tail]
? Tail extends Record<string, unknown>[]
? Mutable<Head> & Omit<Reduce<Tail, TAcc>, keyof Head>
: never
: never;
type Mutable<T> = T extends Readonly<infer U> ? U : T;