---
title: Next.js
description: How to integrate Varlock with Next.js for secure, type-safe environment management
---
import SyncedCodeTabs from '@/components/SyncedCodeTabs.astro'
import { TabItem, Tabs, Steps } from "@astrojs/starlight/components";
import Badge from '@/components/Badge.astro';
import ExecCommandWidget from '@/components/ExecCommandWidget.astro';
import InstallJsDepsWidget from '@/components/InstallJsDepsWidget.astro';

<div class="page-badges">
  <Badge npmPackage="@varlock/nextjs-integration" />
</div>

Varlock provides a huge upgrade over the [default Next.js environment variable tooling](https://nextjs.org/docs/pages/guides/environment-variables) - adding validation, type safety, flexible multi-environment management, log redaction, leak detection, and more.

To integrate varlock into a Next.js application, you must use our [`@varlock/nextjs-integration`](https://www.npmjs.com/package/@varlock/nextjs-integration) package. This package provides a drop-in replacement for [`@next/env`](https://www.npmjs.com/package/@next/env), the internal package that handles .env loading, plus a small config plugin which injects our additional security features.

## Setup

:::caution[Requirements]
- Node.js v22 or higher
- Next.js v14 or higher (v15 and v16 fully supported, including Turbopack)
:::

<Steps>

1. **Install varlock and the Next.js integration package**
    <InstallJsDepsWidget packages="@varlock/nextjs-integration varlock" />

1. **Run `varlock init` to set up your `.env.schema` file**

    This will guide you through setting up your `.env.schema` file, based on your existing `.env` file(s). Make sure to review it carefully.

    <ExecCommandWidget command="varlock init" showBinary={false} />

1. **Override `@next/env` with our drop-in replacement**

    Next.js does not have APIs we can hook into, so we must override their internal .env-loading package.
    Overriding dependencies is a bit different for each package manager:

    <Tabs syncKey='package-manager'>
      <TabItem label="npm">
        See [NPM overrides docs](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides)

        ```diff lang="json" title="package.json"
        {
        +  "overrides": {
        +    "next": {
        +      "@next/env": "npm:@varlock/nextjs-integration"
        +    }
        +  }
        }
        ```
      </TabItem>

      <TabItem label="yarn">
        See [yarn resolutions docs](https://yarnpkg.com/configuration/manifest#resolutions)

        ```diff lang="json" title="root/package.json"
        {
        +  "resolutions": {
        +    "**/@next/env": "npm:@varlock/nextjs-integration"
        +  },
        }
        ```
        **In a monorepo, this override must be done in the monorepo's root package.json file!**
      </TabItem>

      <TabItem label="pnpm">
        <Tabs syncKey='pnpm-version'>
          <TabItem label="pnpm version 10+">
            See [pnpm v10 overrides docs](https://pnpm.io/settings#overrides)

            ```diff lang="yaml" title="root/pnpm-workspace.yaml"
            packages: # <- ⚠️ this field is also required
              - .     # set this to '.' if not in a monorepo
            +overrides:
            +  "@next/env": "npm:@varlock/nextjs-integration"
            ```
            **This must be set in `pnpm-workspace.yaml`, which lives at the root of your repo, regardless of whether you are using a monorepo or not.**
          </TabItem>
          <TabItem label="pnpm version 9">
            See [pnpm v9 overrides docs](https://pnpm.io/9.x/package_json#pnpmoverrides)

            ```diff lang="json" title="root/package.json"
            {
            +  "pnpm": {
            +    "overrides": {
            +      "@next/env": "npm:@varlock/nextjs-integration"
            +    }
            +  }
            }
            ```
            **In a monorepo, this override must be done in the monorepo's root package.json file!**
          </TabItem>
        </Tabs>
      </TabItem>
      <TabItem label="bun">
        See [Bun overrides docs](https://bun.com/docs/pm/overrides)

        ```diff lang="json" title="package.json"
        {
        +  "overrides": {
        +    "@next/env": "npm:@varlock/nextjs-integration"
        +  }
        }
        ```
      </TabItem>
    </Tabs>

    Then re-run your package manager's install command to apply the override:
    <SyncedCodeTabs
      group="package-manager"
      tabs={[
        {
          label: "npm",
          code: "npm install",
          lang: "bash",
        },
        {
          label: "yarn",
          code: "yarn install",
          lang: "bash",
        },
        {
          label: "pnpm",
          code: "pnpm install",
          lang: "bash",
        },
        {
          label: "bun",
          code: "bun install",
          lang: "bash",
        },
      ]}
    />


1. **Enable the Next.js config plugin**

    At this point, varlock will now load your .env files into `process.env`.
    But to get the full benefits of this integration, you must add `varlockNextConfigPlugin` to your `next.config.*` file.

    ```diff lang="ts" title="next.config.ts" "varlockNextConfigPlugin"
    import type { NextConfig } from "next";
    +import { varlockNextConfigPlugin } from '@varlock/nextjs-integration/plugin';

    const nextConfig: NextConfig = {
      // your existing config...
    };

    -export default nextConfig;
    +export default varlockNextConfigPlugin()(nextConfig);
    ```



</Steps>

---

## Accessing environment variables

You can continue to use `process.env.SOMEVAR` as usual, but we recommend using Varlock's imported `ENV` object for better type-safety and improved developer experience:

```ts title="example.ts"
import { ENV } from 'varlock/env';

console.log(process.env.SOMEVAR); // 🆗 still works
console.log(ENV.SOMEVAR);         // ✨ recommended
```

:::caution
If you are not using the `varlockNextConfigPlugin`, only `process.env` will work.
:::

### Type-safety and IntelliSense

To enable type-safety and IntelliSense for your env vars, enable the [`@generateTypes` root decorator](/reference/root-decorators/#generatetypes) in your `.env.schema`. Note that if your schema was created using `varlock init`, it will include this by default.

```diff lang="env-spec" title=".env.schema"
+# @generateTypes(lang='ts', path='env.d.ts')
# ---
# your config items...
```

#### Why use `ENV` instead of `process.env`?
- Non-string values (e.g., number, boolean) are properly typed and coerced
- All non-sensitive items are replaced at build time (not just `NEXT_PUBLIC_`)
- Better error messages for invalid or unavailable keys
- Enables future DX improvements and tighter control over what is bundled

---

## Managing multiple environments

Varlock can load multiple _environment-specific_ `.env` files (e.g., `.env.development`, `.env.preview`, `.env.production`).

By default, the environment flag is determined as follows (matching Next.js):
- `test` if `NODE_ENV` is `test`
- `development` if running `next dev`
- `production` otherwise

:::tip
Without a custom env flag, you cannot use non-production env files (like `.env.preview`, `.env.staging`) for non-prod deployments.
:::

Instead, we recommend explicitly setting your own environment flag using the [`@currentEnv` root decorator](/reference/root-decorators/#currentenv), e.g. `APP_ENV`. See the [environments guide](/guides/environments) for more information.

:::note[Loading `.env.local` in `test` environment]
Next.js makes [a special exception](https://nextjs.org/docs/pages/guides/environment-variables#test-environment-variables) to skip loading `.env.local` if the current environment is `test`.

Varlock does not (following Vite's lead) - but you may explicitly recreate that behavior:

```env-spec title=".env.local"
# @disable=forEnv(test)
```
:::

:::note[Precedence of `.env.local` vs `.env.[currentEnv]`]
Next.js swaps the order of precedence for `.env.local` vs `.env.[currentEnv]` compared to Varlock.
:::

### Setting the environment flag

When running locally, or on a platform you control, you can set the env flag explicitly as an environment variable. However on some cloud platforms, there is a lot of magic happening, and the ability to set environment variables per branch is limited. In these cases you can use functions to transform env vars injected by the platform, like a current branch name, into the value you need.

#### Local/custom scripts

You can set the env var explicitly when you run a command, but often you will set it in `package.json` scripts:

```diff lang="json" title="package.json" add=/APP_ENV=[a-z]+/
"scripts": {
  "build:preview": "APP_ENV=preview next build",
  "start:preview": "APP_ENV=preview next start",
  "build:prod": "APP_ENV=production next build",
  "start:prod": "APP_ENV=production next start",
  "test": "APP_ENV=test jest"
}
```

#### Vercel

You can use the injected `VERCEL_ENV` variable to match their concept of environment types:

```env-spec title=".env.schema"
# @currentEnv=$APP_ENV
# ---
# @type=enum(development, preview, production)
VERCEL_ENV=
# @type=enum(development, preview, production, test)
APP_ENV=fallback($VERCEL_ENV, development)
```

For more granular environments, use the branch name in `VERCEL_GIT_COMMIT_REF` (see Cloudflare example below).

#### Cloudflare Workers Build

Use the branch name in `WORKERS_CI_BRANCH` to determine the environment:

```env-spec title=".env.schema"
# @currentEnv=$APP_ENV
# ---
WORKERS_CI_BRANCH=
# @type=enum(development, preview, production, test)
APP_ENV=remap($WORKERS_CI_BRANCH,
  "main", production,
  /.*/, preview,
  development
)
```

---

## Managing sensitive config values

Next.js uses the `NEXT_PUBLIC_` prefix to determine which env vars are public (bundled for the browser). Varlock lets you control this using decorators.

Set [`@defaultSensitive`](/reference/root-decorators/#defaultsensitive) and mark specific items [@senstive](/reference/item-decorators/#sensitive):

```diff lang="env-spec" title=".env.schema"
+# @defaultSensitive=true
# ---
SECRET_FOO= # sensitive by default
# @sensitive=false
NON_SECRET_FOO=
```

Or, if you'd like to continue using Next.js's prefix behavior:

```diff lang="env-spec" title=".env.schema"
+# @defaultSensitive=inferFromPrefix('NEXT_PUBLIC_')
# ---
FOO= # sensitive
NEXT_PUBLIC_FOO= # non-sensitive, due to prefix
```


:::caution[Bundling behavior]
All non-sensitive items are bundled at build time via `ENV`, while `process.env` replacements only include `NEXT_PUBLIC_`-prefixed items.
:::


## Extra setup for standalone mode ||standalone||

**⚠️ This is only needed if you are using `output: standalone`**

Next's standalone build command will not copy all our `.env` files to the `.next/standalone` directory, so we must copy them manually. Add this to your build command:
```diff lang="json" title="package.json" add=/ && [^"]+/
{
  "scripts": {
    "build": "next build && cp .env.* .next/standalone",
  }
}
```
_you may need to adjust if you don't want to copy certain .local files_


Standalone builds do not copy dependency binaries, and varlock depends on the CLI to load.
So wherever you are booting your standalone server, you will also need to [install the varlock binary](/getting-started/installation/) and boot your server via [`varlock run`](/reference/cli-commands/#run)

```bash
varlock run -- node .next/standalone/server.js
```


---
## Troubleshooting

- ❌ `process.env.__VARLOCK_ENV is not set`
  <br/>💡 This error appears when the `@next/env` override has not been set up properly
  - You may need to re-run your package manager's install command
  - If using pnpm, check if you are using pnpm v9 or v10, because overrides config changed (see above)
- ❌ `Error [ERR_REQUIRE_ESM]: require() of ES Module ...`
  <br/>💡 Varlock requires node v22 or higher - which has better CJS/ESM interoperability
- ❌ `Property 'SOMEVAR' does not exist on type 'TypedEnvSchema'`
  <br/>💡 If the item does exist in your schema, then the generated types are not being loaded properly by TypeScript
  - make sure the [`@generateTypes` root decorator](/reference/root-decorators/#generatetypes) is enabled
  - ensure the path to the generated types file is included in your `tsconfig.json`


---

## Reference
- [Root decorators reference](/reference/root-decorators)
- [Item decorators reference](/reference/item-decorators)
- [Functions reference](/reference/functions)
- [Next.js environment variable docs](https://nextjs.org/docs/pages/guides/environment-variables)
