Skip to content

Next.js

Varlock provides a huge upgrade over the default Next.js environment variable tooling - 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 package. This package provides a drop-in replacement for @next/env, the internal package that handles .env loading, plus a small config plugin which extends the Next.js webpack config to inject our additional security features.

  1. Install varlock and the Next.js integration package

    Terminal window
    npm install @varlock/nextjs-integration varlock
  2. 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.

    Terminal window
    npm exec -- varlock init
  3. Override @next/env with our drop-in replacement

    See NPM overrides docs

    package.json
    {
    "overrides": {
    "@next/env": "npm:@varlock/nextjs-integration"
    }
    }
  4. 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.ts (or .js) file.

    next.config.ts
    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);

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:

example.ts
import { ENV } from 'varlock/env';
console.log(process.env.SOMEVAR); // 🆗 still works
console.log(ENV.SOMEVAR); // ✨ recommended

To enable type-safety and IntelliSense for your env vars, enable the @generateTypes root decorator in your .env.schema. Note that if your schema was created using varlock init, it will include this by default.

.env.schema
# @generateTypes(lang='ts', path='env.d.ts')
# ---
# your config items...

This generates types for both process.env and 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

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

Instead, we recommend explicitly setting your own environment flag using the @envFlag root decorator, e.g. APP_ENV. See the environments guide for more information.

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.

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

package.json
"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"
}

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

.env.schema
# @envFlag=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).

Use the branch name in WORKERS_CI_BRANCH to determine the environment:

.env.schema
# @envFlag=APP_ENV
# ---
WORKERS_CI_BRANCH=
# @type=enum(development, preview, production, test)
APP_ENV=remap($WORKERS_CI_BRANCH, production="main", preview=regex(.*), development=undefined)

Next.js uses the NEXT_PUBLIC_ prefix to determine which env vars are public (bundled for the browser). Varlock lets you control this with the @defaultSensitive root decorator.

To keep the prefix behavior:

.env.schema
# @defaultSensitive=inferFromPrefix('NEXT_PUBLIC_')
# ---
FOO= # sensitive
NEXT_PUBLIC_FOO= # non-sensitive, due to prefix

Or, set a default and explicitly mark items:

.env.schema
# @defaultSensitive=true
# ---
SECRET_FOO= # sensitive by default
# @sensitive=false
NON_SECRET_FOO=

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

The standalone build process will not copy all our .env files to the .next/standalone directory, so we must copy them manually. Add this to your build command:

package.json
{
"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 and boot your server via varlock run

Terminal window
varlock run -- node .next/standalone/server.js