Skip to content

Repository files navigation

next-safe-env logo

next-safe-env

Typed, validated environment variables for Next.js and Node.js. Crash at startup, never at runtime.


npm version bundle size license tests zero dependencies TypeScript


The Problem

Every Next.js and Node.js project has the same boilerplate:

const DATABASE_URL = process.env.DATABASE_URL
if (!DATABASE_URL) throw new Error('Missing DATABASE_URL')

const PORT = parseInt(process.env.PORT ?? '3000', 10)
if (isNaN(PORT)) throw new Error('PORT must be a number')

process.env.X is always string | undefined - no types, no autocomplete. Missing or malformed vars surface mid-request, not at startup. Nothing stops you from reading a server secret in a client component and getting a silent undefined in the browser. Every project re-writes the same validation logic with no single place to audit what the app needs to run.

next-safe-env fixes all of this with a single function call.


Why next-safe-env?

Feature next-safe-env t3-env envalid dotenv + Zod
Zero dependencies
Next.js App Router support Partial
Server/client TypeScript split Manual
Edge Runtime adapter
Fluent validator API Schema-based Custom Schema-based
Pretty error output Partial Manual
Bundle size < 5 kB ~50 kB+ ~10 kB ~50 kB+
Auto-enforce NEXT_PUBLIC_ prefix Manual
Zod interop Optional ✅ Required Required
ClientEnv<T> server-only branding Partial Manual
Vite adapter Manual
CLI (check / init)

If your project already uses a schema validation library, tools like t3-env or envalid integrate well with your existing setup. next-safe-env is for teams that want typed, validated env vars with no additional dependencies - the full feature set ships in under 5 kB.


Requirements

  • Node.js 18+
  • TypeScript 5.x

No runtime dependencies.


Installation

npm install next-safe-env
# or
pnpm add next-safe-env
# or
yarn add next-safe-env

next-safe-env validates what is already in process.env. It does not load .env files. For that, use Next.js's built-in .env support or dotenv.


Quick Start

// src/env.ts
import { createEnv, str, url, port, bool } from 'next-safe-env'

export const env = createEnv({
  server: {
    DATABASE_URL: url(),                // must be a valid URL
    PORT:         port().default(3000), // coerced to number, defaults to 3000
    NODE_ENV:     str().enum(['development', 'production', 'test']),
  },
  client: {
    NEXT_PUBLIC_APP_NAME:     str().default('My App'),
    NEXT_PUBLIC_ENABLE_DEBUG: bool().default(false),
  },
  runtimeEnv: {
    DATABASE_URL:             process.env.DATABASE_URL,
    PORT:                     process.env.PORT,
    NODE_ENV:                 process.env.NODE_ENV,
    NEXT_PUBLIC_APP_NAME:     process.env.NEXT_PUBLIC_APP_NAME,
    NEXT_PUBLIC_ENABLE_DEBUG: process.env.NEXT_PUBLIC_ENABLE_DEBUG,
  },
})
// anywhere in your app
import { env } from '@/env'

env.DATABASE_URL          // string
env.PORT                  // number - not string
env.NEXT_PUBLIC_APP_NAME  // string

If any variable is missing or invalid, the app refuses to start and prints every problem at once:

[next-safe-env] Environment validation failed - 3 error(s):

  ✗ DATABASE_URL  - Required. Expected a valid URL. Got: "postgres-localhost"
  ✗ JWT_SECRET    - Too short. Must be ≥ 32 characters. Got length: 12
  ✗ SMTP_PORT     - Invalid port. Must be 1–65535. Got: "99999"

CLI

next-safe-env ships a zero-install CLI for validation and scaffolding.

check — validate before you deploy

Imports your compiled env file in an isolated process and exits 0 if all vars are valid, 1 if any fail. Drop it into any CI pipeline to gate deployments:

# Auto-discovers src/env.js then dist/env.js
npx next-safe-env check

# Or point at a specific file
npx next-safe-env check ./dist/env.js
[next-safe-env] Checking src/env.js...

[next-safe-env] Environment validation failed — 2 error(s):

  ✗ DATABASE_URL  — Expected valid URL. Got: "postgres-localhost"
  ✗ JWT_SECRET    — Expected length >= 32. Got length: 12

[next-safe-env] ✗ Validation failed.

init — generate src/env.ts and .env.example

An interactive scaffold that asks which variables your app needs, their types, defaults, and constraints — then writes a ready-to-use env.ts and a commented .env.example:

npx next-safe-env init

# Custom output path
npx next-safe-env init --output config/env.ts

The generated .env.example includes inline comments for every variable so new contributors know exactly what to fill in:

# DATABASE_URL — required valid URL
DATABASE_URL=

# PORT — required port number (1–65535)
# Default: 3000
PORT=3000

# NODE_ENV — required string
# Allowed values: development | production | test
NODE_ENV=

Documentation

The full documentation is available at next-safe-env.dev.

Guides

  • Getting Started - Install and validate your first env var in minutes
  • Next.js App Router - Server/client splitting with automatic NEXT_PUBLIC_ enforcement
  • Node.js - Plain Node.js servers, APIs, and CLI scripts
  • Edge Runtime - Vercel Edge Runtime and Next.js Middleware
  • Vite - Non-Next.js React apps with import.meta.env
  • Zod Interop - Pass z.object(...) schemas directly, no rewrites needed
  • Testing - Skip validation in test environments without removing your schema
  • CLI - check and init commands for CI validation and interactive scaffolding

API Reference

  • Validators - str, num, bool, url, port and their chainable rules
  • createEnv() - Full reference for every configuration option
  • TypeScript Types - ServerOnly<T>, ClientEnv<T>, and all exported types

Concepts

  • Adapters - How Next.js, Node.js, Edge Runtime, and Vite adapters work
  • Server & Client Split - How env vars are separated and protected per runtime context
  • Error Handling - Validation errors, pretty output, and custom error handlers

next-safe-env was built because process.env.X should never be string | undefined in a typed codebase - and getting full validation, type inference, and Next.js adapter support shouldn't require adding new dependencies to do it.

MIT © 2026

Releases

Packages

Contributors

Languages