npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

next-dynenv

v4.0.5

Published

Dynamic runtime environment variables for Next.js - populate your environment at runtime, not build time.

Readme

🌐 next-dynenv

Next.js React TypeScript License

npm version CI Downloads

Effortlessly populate your environment at runtime, not just at build time.

⚡ Installation🚀 Getting Started🛠 Advanced Usage🚀 Deployment

Dynamic runtime environment variables for Next.js. This package is a Next.js 15/16 & React 19 compatible fork of next-runtime-env by Expatfile.tax LLC.

✨ Highlights

  • Isomorphic Design: Works seamlessly on both server and browser, and even in middleware
  • Next.js 15/16 & React 19 Ready: Fully compatible with the latest Next.js features including async server components
  • .env Friendly: Use .env files during development, just like standard Next.js
  • Type-Safe Parsers: Convert environment strings to booleans, numbers, arrays, JSON, URLs, and enums
  • Secure by Default: XSS protection via JSON escaping, immutable runtime values with Object.freeze
  • Zero Config: Works out of the box with sensible defaults

🤔 Why next-dynenv?

In the modern software development landscape, the Build once, deploy many philosophy is key. This principle, essential for easy deployment and testability, is a cornerstone of continuous delivery and is embraced by the twelve-factor methodology. However, front-end development, particularly with Next.js, often lacks support for this - requiring separate builds for different environments. next-dynenv bridges this gap.

📦 How It Works

next-dynenv dynamically injects environment variables into your Next.js application at runtime. This approach adheres to the "build once, deploy many" principle, allowing the same build to be used across various environments without rebuilds.

🔖 Version Guide

Original project versions (unmaintained):

📦 Installation

npm install next-dynenv
# or
pnpm add next-dynenv
# or
yarn add next-dynenv

🚀 Getting Started

Step 1: Add the Script Component

In your root layout (app/layout.tsx), add the PublicEnvScript component:

// app/layout.tsx
import { PublicEnvScript } from 'next-dynenv'
import type { ReactNode } from 'react'

export default function RootLayout({ children }: { children: ReactNode }) {
    return (
        <html lang="en">
            <head>
                <PublicEnvScript />
            </head>
            <body>{children}</body>
        </html>
    )
}

The PublicEnvScript component automatically exposes all environment variables prefixed with NEXT_PUBLIC_ to the browser. For custom variable exposure, refer to the Exposing Custom Environment Variables guide.

Step 2: Use Environment Variables

In Client Components

// app/components/ClientComponent.tsx
'use client'

import { env } from 'next-dynenv'

export default function ClientComponent() {
    const apiUrl = env('NEXT_PUBLIC_API_URL')
    const debug = env('NEXT_PUBLIC_DEBUG_MODE')

    return (
        <div>
            <p>API URL: {apiUrl}</p>
            <p>Debug Mode: {debug}</p>
        </div>
    )
}

In Server Components (Next.js 15+)

// app/components/ServerComponent.tsx
import { env } from 'next-dynenv'

export default async function ServerComponent() {
    // Server components in Next.js 15 can be async
    const apiUrl = env('NEXT_PUBLIC_API_URL')
    const secretKey = env('SECRET_API_KEY') // Server-side only variables also work

    return (
        <div>
            <p>API URL: {apiUrl}</p>
            {/* Never expose secret keys to the client */}
        </div>
    )
}

Note: In Next.js 15, server components can be async by default. The env() function works seamlessly in both sync and async server components.

In Middleware

// middleware.ts
import { env } from 'next-dynenv'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
    // env() works in middleware too!
    const apiUrl = env('NEXT_PUBLIC_API_URL')
    const featureFlag = env('NEXT_PUBLIC_ENABLE_FEATURE')

    // Your middleware logic here
    if (featureFlag === 'true') {
        // Feature-specific logic
    }

    return NextResponse.next()
}

export const config = {
    matcher: '/api/:path*',
}

Note: The env() function works in all Next.js contexts - server components, client components, API routes, and middleware. It's safe to use everywhere and provides a consistent API across your application.

Default Values

The env() function accepts an optional default value:

import { env } from 'next-dynenv'

// Returns 'https://api.default.com' if NEXT_PUBLIC_API_URL is undefined
const apiUrl = env('NEXT_PUBLIC_API_URL', 'https://api.default.com')

// Default values work in all contexts (client, server, middleware)
const timeout = env('NEXT_PUBLIC_TIMEOUT', '5000')

Required Environment Variables

Use requireEnv() when a variable must be defined:

import { requireEnv } from 'next-dynenv'

// Throws descriptive error if NEXT_PUBLIC_API_URL is undefined
const apiUrl = requireEnv('NEXT_PUBLIC_API_URL')
// Error: "Required environment variable 'NEXT_PUBLIC_API_URL' is not defined."

Server-Only Variables

Use serverOnly() for non-public environment variables in code that runs on both client and server. Unlike env(), this function never throws in the browser—it gracefully returns the fallback value:

import { serverOnly } from 'next-dynenv'

// Returns the actual value on server, fallback on client
const dbUrl = serverOnly('DATABASE_URL', 'postgresql://localhost:5432/dev')
const apiSecret = serverOnly('API_SECRET_KEY') // undefined on client

This is particularly useful for shared configuration modules with lazy evaluation:

import { env, serverOnly } from 'next-dynenv'
import { z } from 'zod'

const configSchema = z.object({
    supabaseUrl: z.string().url(),
    supabaseAnonKey: z.string(),
    // Server-only: returns undefined on client, real value on server
    supabaseServiceKey: z.string().optional(),
})

// Safe to import anywhere—evaluates lazily
export const config = lazy(() =>
    configSchema.parse({
        supabaseUrl: env('NEXT_PUBLIC_SUPABASE_URL'),
        supabaseAnonKey: env('NEXT_PUBLIC_SUPABASE_ANON_KEY'),
        supabaseServiceKey: serverOnly('SUPABASE_SERVICE_KEY'),
    }),
)

Type-Safe Parsers

Use envParsers to convert environment strings to typed values:

import { envParsers } from 'next-dynenv'

// Boolean - recognizes 'true', '1', 'yes', 'on' (case-insensitive)
const debug = envParsers.boolean('NEXT_PUBLIC_DEBUG') // false by default
const enabled = envParsers.boolean('NEXT_PUBLIC_FEATURE', true) // custom default

// Number - integers and floats
const port = envParsers.number('NEXT_PUBLIC_PORT', 3000)
const ratio = envParsers.number('NEXT_PUBLIC_RATIO', 1.0)

// Array - comma-separated values (trims whitespace, filters empty)
const features = envParsers.array('NEXT_PUBLIC_FEATURES')
// 'auth, payments, analytics' → ['auth', 'payments', 'analytics']

// JSON - parse complex objects
interface Config {
    api: string
    timeout: number
}
const config = envParsers.json<Config>('NEXT_PUBLIC_CONFIG')

// URL - validates and returns URL string
const apiUrl = envParsers.url('NEXT_PUBLIC_API_URL')
const cdn = envParsers.url('NEXT_PUBLIC_CDN', 'https://cdn.default.com')

// Enum - restrict to allowed values with type safety
type Environment = 'development' | 'staging' | 'production'
const appEnv = envParsers.enum<Environment>(
    'NEXT_PUBLIC_ENV',
    ['development', 'staging', 'production'],
    'development', // default value
)

type LogLevel = 'debug' | 'info' | 'warn' | 'error'
const logLevel = envParsers.enum<LogLevel>('NEXT_PUBLIC_LOG_LEVEL', ['debug', 'info', 'warn', 'error'])

All parsers work isomorphically (server and client) and provide clear error messages for invalid values.

🛠 Advanced Usage

Exposing Non-Prefixed Variables

Need to expose environment variables without the NEXT_PUBLIC_ prefix? Check out the Making Environment Variables Public guide.

Custom Variable Exposure

For fine-grained control over which variables are exposed to the browser, see the Exposing Custom Environment Variables guide.

🚀 Deployment Guide

Docker Deployment

When deploying with Docker, pass environment variables at runtime:

# Dockerfile
FROM node:20-alpine AS base

# Install dependencies
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Build application
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# Production image
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

EXPOSE 3000

ENV PORT=3000

CMD ["node", "server.js"]

Run with environment variables:

docker run -p 3000:3000 \
  -e NEXT_PUBLIC_API_URL=https://api.example.com \
  -e NEXT_PUBLIC_APP_VERSION=1.0.0 \
  your-app:latest

Vercel Deployment

  1. Add environment variables in your Vercel project settings
  2. Set different values for Preview, Development, and Production environments
  3. Deploy your application - next-dynenv will automatically use the runtime values
vercel env add NEXT_PUBLIC_API_URL production
vercel env add NEXT_PUBLIC_API_URL preview

Netlify Deployment

Add environment variables in your Netlify site settings:

  1. Go to Site settings → Environment variables
  2. Add your NEXT_PUBLIC_* variables
  3. Deploy contexts can have different values (Production, Deploy Previews, Branch deploys)

AWS Amplify

Configure environment variables in the Amplify Console:

  1. Navigate to App settings → Environment variables
  2. Add variables for each branch as needed
  3. Amplify will inject these at runtime during deployment

Generic Static Hosting

For static exports with runtime environment support:

  1. Build your application: npm run build
  2. Set environment variables on your hosting platform
  3. The variables will be available at runtime through next-dynenv

🔒 Security Considerations

Built-in Security Features

This library includes multiple layers of security by default:

  • XSS Protection: All environment values are JSON-escaped before injection, preventing script injection attacks
  • Immutable Runtime Values: Environment values are wrapped with Object.freeze(), preventing modification after initialization
  • Strict Prefix Enforcement: Only NEXT_PUBLIC_* variables are exposed to the browser; accessing private variables throws an error

Never Expose Secrets to the Browser

Critical: Only variables prefixed with NEXT_PUBLIC_ are exposed to the browser. Never expose sensitive data:

// ❌ WRONG - Don't try to access secrets in client components
'use client'
const apiKey = env('SECRET_API_KEY') // Throws error in browser!

// ✅ CORRECT - Use secrets only server-side
export async function getData() {
    const apiKey = env('SECRET_API_KEY') // Works in server components/API routes
    // ... fetch data securely
}

Environment Variable Validation

Use requireEnv() for required variables, or validate multiple at once:

// Using requireEnv() - throws if undefined
import { requireEnv } from 'next-dynenv'

const apiUrl = requireEnv('NEXT_PUBLIC_API_URL')

// Validating multiple variables
import { env } from 'next-dynenv'

export function validateEnv() {
    const required = ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_APP_ID']

    for (const key of required) {
        if (!env(key)) {
            throw new Error(`Missing required environment variable: ${key}`)
        }
    }
}

Content Security Policy

If using CSP, ensure inline scripts are allowed for the PublicEnvScript:

// next.config.js
const ContentSecurityPolicy = `
  script-src 'self' 'unsafe-inline';
`

module.exports = {
    async headers() {
        return [
            {
                source: '/:path*',
                headers: [
                    {
                        key: 'Content-Security-Policy',
                        value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim(),
                    },
                ],
            },
        ]
    },
}

🐛 Troubleshooting

Variables Undefined in Browser

Problem: Environment variables return undefined in client components.

Solutions:

  1. Ensure variables have the NEXT_PUBLIC_ prefix
  2. Verify PublicEnvScript is in your root layout's <head>
  3. Check that variables are set in your environment (.env.local, hosting platform, etc.)
  4. Restart your development server after adding new environment variables

Variables Not Updating in Production

Problem: Changed environment variables don't reflect in deployed application.

Solutions:

  1. For Docker: Restart containers with new environment variables
  2. For Vercel/Netlify: Trigger a new deployment or redeploy
  3. Clear CDN cache if using one
  4. Verify variables are set in the correct deployment environment

TypeScript Type Errors

Problem: TypeScript complains about env() return type.

Solution: The env() function returns string | undefined. Handle this explicitly:

const apiUrl = env('NEXT_PUBLIC_API_URL') ?? 'https://default-api.com'

// Or with type assertion if you're certain it exists
const apiUrl = env('NEXT_PUBLIC_API_URL')!

Build-Time vs Runtime Variables

Problem: Confusion about when variables are available.

Explanation:

  • Build-time: Variables are baked into the bundle during next build
  • Runtime: Variables are injected when the application starts
  • next-dynenv provides runtime access, allowing the same build to work in multiple environments

Server vs Client Environment Access

Problem: Different behavior between server and client.

Key Differences:

  • Server-side contexts (server components, API routes, middleware):

    • Can access ALL environment variables via env() or process.env
    • Both private and public (NEXT_PUBLIC_*) variables are available
  • Client-side contexts (client components, browser):

    • Can only access NEXT_PUBLIC_* variables via env()
    • Private variables are not available for security reasons
    • Attempting to access private variables throws an error

Recommendation: Use env() everywhere for consistency. It works in all contexts and provides better error messages when misused

📚 Additional Resources

👷 Maintenance

This fork is maintained by Stefanie Jane (@hyperb1iss).

🙏 Acknowledgments

  • Original Project: next-runtime-env by Expatfile.tax - All credit for the original implementation and core concepts
  • Inspiration: react-env project
  • Context Provider: Thanks to @andonirdgz for the innovative context provider idea

If you find this useful, buy me a Monster Ultra Violet ⚡