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 injects our additional security features.
-
Install varlock and the Next.js integration package
Terminal window npm install @varlock/nextjs-integration varlockTerminal window yarn add @varlock/nextjs-integration varlockTerminal window pnpm add @varlock/nextjs-integration varlockTerminal window vlt install @varlock/nextjs-integration varlock -
Run
varlock initto set up your.env.schemafileThis will guide you through setting up your
.env.schemafile, based on your existing.envfile(s). Make sure to review it carefully.Terminal window npm exec -- varlock initTerminal window yarn exec -- varlock initTerminal window pnpm exec -- varlock initTerminal window vlx -- varlock init -
Override
@next/envwith our drop-in replacementNext.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:
package.json {"overrides": {"next": {"@next/env": "npm:@varlock/nextjs-integration"}}}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!
root/pnpm-workspace.yaml packages: # <- ⚠️ this field is also required- . # set this to '.' if not in a monorepooverrides:"@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.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!
Then re-run your package manager’s install command to apply the override:
Terminal window npm installTerminal window yarn installTerminal window pnpm install -
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 addvarlockNextConfigPluginto yournext.config.*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);
Accessing environment variables
Section titled “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:
import { ENV } from 'varlock/env';
console.log(process.env.SOMEVAR); // 🆗 still worksconsole.log(ENV.SOMEVAR); // ✨ recommendedType-safety and IntelliSense
Section titled “Type-safety and IntelliSense”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.
# @generateTypes(lang='ts', path='env.d.ts')# ---# your config items...Why use ENV instead of process.env?
Section titled “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
Section titled “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):
testifNODE_ENVistestdevelopmentif runningnext devproductionotherwise
Instead, we recommend explicitly setting your own environment flag using the @currentEnv root decorator, e.g. APP_ENV. See the environments guide for more information.
Setting the environment flag
Section titled “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
Section titled “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:
"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
Section titled “Vercel”You can use the injected VERCEL_ENV variable to match their concept of environment types:
# @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
Section titled “Cloudflare Workers Build”Use the branch name in WORKERS_CI_BRANCH to determine the environment:
# @currentEnv=$APP_ENV# ---WORKERS_CI_BRANCH=# @type=enum(development, preview, production, test)APP_ENV=remap($WORKERS_CI_BRANCH, production="main", preview=regex(.*), development=undefined)Managing sensitive config values
Section titled “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 with the @defaultSensitive root decorator.
Set a default and explicitly mark items:
# @defaultSensitive=true# ---SECRET_FOO= # sensitive by default# @sensitive=falseNON_SECRET_FOO=Or, if you’d like to continue using Next.js’s prefix behavior:
# @defaultSensitive=inferFromPrefix('NEXT_PUBLIC_')# ---FOO= # sensitiveNEXT_PUBLIC_FOO= # non-sensitive, due to prefixExtra setup for standalone mode
Section titled “Extra setup for standalone mode”⚠️ 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:
{ "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
varlock run -- node .next/standalone/server.jsTroubleshooting
Section titled “Troubleshooting”- ❌
process.env.__VARLOCK_ENV is not set
💡 This error appears when the@next/envoverride 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 ...
💡 Varlock requires node v22 or higher - which has better CJS/ESM interoperability - ❌
Property 'SOMEVAR' does not exist on type 'TypedEnvSchema'
💡 If the item does exist in your schema, then the generated types are not being loaded properly by TypeScript- make sure the
@generateTypesroot decorator is enabled - ensure the path to the generated types file is included in your
tsconfig.json
- make sure the