> ## Documentation Index
> Fetch the complete documentation index at: https://next-safe-env.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Use next-safe-env in a Next.js App Router project

> Set up typed, validated environment variables in Next.js with automatic NEXT_PUBLIC_ prefix enforcement and server/client bundle separation.

`next-safe-env` gives you a single `env` object where every variable is validated at startup, typed precisely, and automatically split between server and client bundles. Instead of scattering `process.env.X` calls across your codebase and discovering missing values mid-request, you define your schema once in `src/env.ts` and get full TypeScript autocomplete everywhere you import it.

<Steps>
  <Step title="Install next-safe-env">
    Add the package to your project. It has zero runtime dependencies.

    ```bash theme={null}
    npm install next-safe-env
    ```
  </Step>

  <Step title="Create src/env.ts">
    Define your server and client schemas. Pass each `process.env` reference individually in `runtimeEnv` - this lets the Next.js bundler inline each value statically and prevents server vars from leaking into the client bundle.

    ```typescript theme={null}
    // src/env.ts
    import { createEnv, str, bool, url, port } from 'next-safe-env'

    export const env = createEnv({
      server: {
        DATABASE_URL: url(),
        JWT_SECRET:   str().min(32),
        SMTP_PORT:    port().default(587),
        NODE_ENV:     str().enum(['development', 'production', 'test']),
        REDIS_URL:    url().optional(),
      },
      client: {
        NEXT_PUBLIC_API_URL:      url(),
        NEXT_PUBLIC_APP_NAME:     str().default('My App'),
        NEXT_PUBLIC_ENABLE_DEBUG: bool().default(false),
      },
      // Pass individual process.env.VAR references, not the whole process.env object.
      // This lets the bundler inline each value statically, which prevents server
      // vars from being included in the client bundle.
      runtimeEnv: {
        DATABASE_URL:             process.env.DATABASE_URL,
        JWT_SECRET:               process.env.JWT_SECRET,
        SMTP_PORT:                process.env.SMTP_PORT,
        NODE_ENV:                 process.env.NODE_ENV,
        REDIS_URL:                process.env.REDIS_URL,
        NEXT_PUBLIC_API_URL:      process.env.NEXT_PUBLIC_API_URL,
        NEXT_PUBLIC_APP_NAME:     process.env.NEXT_PUBLIC_APP_NAME,
        NEXT_PUBLIC_ENABLE_DEBUG: process.env.NEXT_PUBLIC_ENABLE_DEBUG,
      },
    })
    ```

    <Note>
      `next-safe-env` validates what is already in `process.env`. It does not load `.env` files. Next.js loads `.env` files automatically, so no extra setup is needed.
    </Note>
  </Step>

  <Step title="Use it in a server context">
    Import `env` in any server-side file - API routes, Server Components, server actions. All vars from both schemas are available here.

    ```typescript theme={null}
    // app/api/auth/route.ts - server context, all vars available
    import { env } from '@/env'

    export async function POST() {
      const db   = await connectDB(env.DATABASE_URL)  // string
      const port = env.SMTP_PORT                       // number, not string
      const debug = env.NEXT_PUBLIC_ENABLE_DEBUG       // boolean
    }
    ```
  </Step>

  <Step title="Use it in a client component">
    Import the same `env` object in Client Components. Client vars are fully typed and autocomplete. Server vars carry the `ServerOnly<T>` brand, which is visible in IDE tooltips.

    ```typescript theme={null}
    // app/components/Header.tsx - client component
    import { env } from '@/env'

    export function Header() {
      // ✅ Client vars are fully typed and autocomplete
      return <h1>{env.NEXT_PUBLIC_APP_NAME}</h1>

      // ⚠️  Server vars carry the ServerOnly<T> brand - visible in IDE tooltips.
      // For a hard compile error that prevents client components from reading
      // server vars, add `import 'server-only'` at the top of your env.ts file.
    }
    ```

    <Note>
      The `ServerOnly<T>` brand is visible in IDE tooltips and communicates a variable's origin at a glance, but it does not produce a TypeScript compile error on its own due to structural typing. To get a hard build-time error when `env.ts` is imported in a client bundle, add `import 'server-only'` at the top of `src/env.ts`. Next.js will then throw at build time if the module is included in any client bundle.
    </Note>
  </Step>
</Steps>

## `NEXT_PUBLIC_` prefix enforcement

The `nextjs` adapter checks every key in your `client` schema before any validation runs. If a key is missing the `NEXT_PUBLIC_` prefix, `createEnv` throws immediately:

```
Error: [next-safe-env] Client env var "API_KEY" must be prefixed with NEXT_PUBLIC_.
```

This is a hard fail-fast. Without the prefix, Next.js silently omits the variable from the browser bundle and you get `undefined` at runtime with no warning. `next-safe-env` surfaces the mistake at startup instead.

## Browser context stripping

When your code runs in a browser context (`typeof window !== 'undefined'`), the `nextjs` adapter strips every non-`NEXT_PUBLIC_` key from the returned env object before it reaches your component. This is a runtime safety net layered on top of the TypeScript enforcement - even if a server var somehow ends up referenced in a client bundle, its value will be `undefined` rather than the secret it holds.

On the server side, all vars from both schemas are returned unchanged.

## Adapter auto-detection

You do not need to set `adapter: 'nextjs'` explicitly in most cases. `next-safe-env` auto-detects the correct adapter using this order:

| Condition                                  | Adapter selected          |
| ------------------------------------------ | ------------------------- |
| `process` is undefined or has no `version` | `edge`                    |
| `process.env.NEXT_RUNTIME` is set          | `nextjs`                  |
| Any client key starts with `NEXT_PUBLIC_`  | `nextjs` + `console.warn` |
| Otherwise                                  | `node`                    |

Next.js App Router sets `NEXT_RUNTIME` automatically, so detection is reliable. Set `adapter: 'nextjs'` explicitly if you want to suppress the Pages Router heuristic warning or prefer predictable, self-documenting config.

## What happens when validation fails

If any variable is missing or invalid, the app refuses to start and prints every problem at once. You never see just the first failure.

```
[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"

  Server vars:  2 valid, 3 invalid
  Client vars:  2 valid, 0 invalid

  Set the correct values in your .env file or deployment environment and restart.
```

Fix all reported values in your `.env` file or deployment environment and restart. The full list is always shown before exit.
