Varlock is a big upgrade over the default Next.js environment variable tooling, adding validation, type safety, multi-environment management, log redaction, leak detection, and more.
To integrate varlock into a Next.js application, use our @varlock/nextjs-integration package. It provides a drop-in replacement for @next/env, the internal package that handles .env loading, plus a small config plugin that injects our additional security features.
-
Install varlock and the Next.js integration package
Terminal window npm install @varlock/nextjs-integration varlockTerminal window pnpm add @varlock/nextjs-integration varlockTerminal window bun add @varlock/nextjs-integration varlockTerminal window yarn 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 pnpm exec -- varlock initTerminal window bunx varlock initTerminal window vlx -- varlock initTerminal window yarn exec -- 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!
package.json {"overrides": {"@next/env": "npm:@varlock/nextjs-integration"}}Then re-run your package manager’s install command to apply the override:
Terminal window npm installTerminal window yarn installTerminal window pnpm installTerminal window bun 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 withVarlock = varlockNextConfigPlugin();const nextConfig: NextConfig = {// your existing config...};export default nextConfig;export default withVarlock(nextConfig); -
Verify the override is active
Start your dev server (or run a build) and look at the startup output. When the override is working, varlock reports the
.envfiles it loaded with a banner:next dev - Environments: .env.schema,✨ loaded by varlock ✨If you don’t see
✨ loaded by varlock ✨, or you getCannot find module '@next/env'or__VARLOCK_ENV is not set, the override didn’t take effect. See Troubleshooting below.
Monorepos
Section titled “Monorepos”In a monorepo, the @next/env override from setup step 3 must live at the workspace root so every Next.js app resolves the same replacement. Package-manager overrides apply to all Next.js packages in the workspace, so only add the override once each app you run has its own .env.schema and varlockNextConfigPlugin.
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 @generateTsTypes root decorator in your .env.schema. Note that if your schema was created using varlock init, it will include this by default.
# @generateTsTypes(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, "main", production, /.*/, preview, development)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 using decorators.
Set @defaultSensitive and mark specific items @sensitive:
# @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”Almost all setup issues come down to the @next/env override not being applied. The fixes below are ordered by the symptom you see. Work top to bottom.
- ❌
Error: Cannot find module '@next/env'(or no✨ loaded by varlock ✨banner)
💡 The override was recorded in your lockfile but your package manager didn’t materialize it correctly. This is a known package-manager issue withoverrides/resolutionsfor nested scoped packages. npm in particular can create a dangling symlink undernode_modules/next/node_modules/@next/env, or skip it entirely, when installing against a stale lockfile.- Delete both
node_modulesand your lockfile, then reinstall. Deleting the lockfile alone is not enough:- npm:
rm -rf node_modules package-lock.json && npm install - pnpm:
rm -rf node_modules pnpm-lock.yaml && pnpm install - yarn:
rm -rf node_modules yarn.lock && yarn install - bun:
rm -rf node_modules bun.lock bun.lockb && bun install
- npm:
- Commit the regenerated lockfile.
- Re-verify with the banner above, or directly:
cat node_modules/@next/env/package.jsonshould show"name": "@varlock/nextjs-integration".
- Delete both
- ❌
process.env.__VARLOCK_ENV is not set
💡 The override is missing or in the wrong place, so Next loaded its own@next/envinstead of ours.- Confirm the override config is present and in the correct file for your package manager (see step 3). For pnpm it must be in
pnpm-workspace.yaml(v10) orpackage.json#pnpm.overrides(v9), and in a monorepo it must be at the repo root. - Re-run your package manager’s install command, then re-verify the banner.
- Confirm the override config is present and in the correct file for your package manager (see step 3). For pnpm it must be in
- ❌
Error [ERR_REQUIRE_ESM]: require() of ES Module ...
💡 Varlock requires node v22 or higher, which has better CJS/ESM interoperability - ❌ Every page 500s in dev with a
[turbopack-node]/transforms/transforms.ts ... lint TP1006error, but only when the project has amiddleware.tsfile (Next 15.0–15.4 + Turbopack)
💡 Turbopack on those versions cannot apply JS loaders (which varlock uses) to edge-context files like middleware without breaking dev page rendering. Upgrade tonext@^15.5(where varlock scopes its loader away from edge files automatically) or Next 16 (which fixed the underlying issue), or runnext devwithout--turbopack. - ❌
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
@generateTsTypesroot decorator is enabled - ensure the path to the generated types file is included in your
tsconfig.json
- make sure the