Skip to content

env() ​

Read an environment variable from a server component, route handler, proxy, or client component, with an optional default.

Import ​

ts
import { env } from 'next-dynenv'

Signature ​

ts
function env(key: string): string | undefined
function env<T extends string>(key: string, defaultValue: T): string

Parameters ​

ParameterTypeDescription
keystringVariable name. In the browser it must start with NEXT_PUBLIC_.
defaultValuestringOptional. Returned when the variable is undefined. Narrows the return type.

Returns ​

The variable's value, or defaultValue, or undefined. The default only replaces undefined: a variable set to an empty string returns ''.

Throws ​

In the browser, once window.__ENV exists, any key without the NEXT_PUBLIC_ prefix throws:

text
Environment variable 'API_SECRET_KEY' is not public and cannot be accessed in the browser.
To fix this:
  1. Rename to 'NEXT_PUBLIC_API_SECRET_KEY' if it should be public, or
  2. Use makeEnvPublic('API_SECRET_KEY') in instrumentation.ts, or
  3. Access from a server component or API route instead

For option 2, call makeEnvPublic() from instrumentation.ts, which works in every output mode.

Where it reads from ​

ContextSourcePrefix check
Server components, route handlers, server actions, proxy.ts, instrumentation.tsprocess.envNone
Client components while they render on the server (SSR)process.envNone
Browser, with PublicEnvScript or EnvScript renderedwindow.__ENVKeys must start with NEXT_PUBLIC_
Browser, without either scriptprocess.env, which holds nothing usefulNone, returns the default

Two rows deserve a second look:

  • SSR of client components. A 'use client' component renders on the server first, where env('API_SECRET_KEY') returns the real secret and writes it into the HTML before the browser check ever runs. Only pass NEXT_PUBLIC_ keys to env() inside client components.
  • No script. The context approach does not set window.__ENV, so env() in the browser returns undefined or the default. Read values there with useEnvContext().

Examples ​

Client component ​

tsx
'use client'

import { env } from 'next-dynenv'

export function ApiBadge() {
    const apiUrl = env('NEXT_PUBLIC_API_URL', 'http://localhost:8080')

    return <span>API: {apiUrl}</span>
}

Server component ​

tsx
// app/status/page.tsx
import { env } from 'next-dynenv'

export default async function StatusPage() {
    const res = await fetch(`${env('NEXT_PUBLIC_API_URL')}/status`, {
        headers: { Authorization: `Bearer ${env('API_SECRET_KEY')}` },
    })
    const status: { ok: boolean } = await res.json()

    return <p>Upstream is {status.ok ? 'up' : 'down'}</p>
}

Route handler ​

ts
// app/api/config/route.ts
import { env } from 'next-dynenv'

export function GET() {
    return Response.json({
        apiUrl: env('NEXT_PUBLIC_API_URL'),
        appName: env('NEXT_PUBLIC_APP_NAME', 'My App'),
    })
}

Proxy ​

ts
// proxy.ts (Next.js 16). On Next.js 15, use middleware.ts and export `middleware`.
import { env } from 'next-dynenv'
import { type NextRequest, NextResponse } from 'next/server'

export function proxy(request: NextRequest) {
    if (env('MAINTENANCE_MODE') === 'true') {
        return NextResponse.rewrite(new URL('/maintenance', request.url))
    }

    return NextResponse.next()
}

Type narrowing ​

ts
const maybeUrl = env('NEXT_PUBLIC_API_URL') // string | undefined
const apiUrl = env('NEXT_PUBLIC_API_URL', 'https://api.example.com') // string

Released under the MIT License. Forked from next-runtime-env by Expatfile.tax LLC.