While Nuxt has runtimeConfig to help with environment variables, we think Varlock has more to offer:
- Your
.env.schemais not tied to JavaScript, and is a better place to store this schema info versus yournuxt.config.*file - Facilitates loading and composing multiple
.envfiles - Facilitates setting values and handling multiple environments, not just setting defaults
- More data types and options available
- Leak detection, log redaction, and more security guardrails
To integrate varlock into a Nuxt application, you must use our @varlock/nuxt-integration package, which is a Nuxt module.
-
Install varlock and the Nuxt module
Terminal window npm install @varlock/nuxt-integration varlockTerminal window pnpm add @varlock/nuxt-integration varlockTerminal window bun add @varlock/nuxt-integration varlockTerminal window yarn add @varlock/nuxt-integration varlockTerminal window vlt install @varlock/nuxt-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 -
Enable the Nuxt module
Add
@varlock/nuxt-integrationto yournuxt.config.tsmodules array:nuxt.config.ts export default defineNuxtConfig({modules: ['@varlock/nuxt-integration'],})
Accessing environment variables
Section titled “Accessing environment variables”You can continue to use process.env.SOMEVAR or useRuntimeConfig() 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); // ✨ recommendedWhy 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
- Better error messages for invalid or unavailable keys
- Enables future DX improvements and tighter control over what is bundled
Using useRuntimeConfig()
Section titled “Using useRuntimeConfig()”Existing code and third-party modules often read values through Nuxt’s runtimeConfig. That keeps working alongside varlock: Nuxt applies NUXT_-prefixed env vars (e.g. NUXT_PUBLIC_API_BASE for runtimeConfig.public.apiBase) onto declared runtime config keys at server start, and it reads them from process.env. So any NUXT_-prefixed item in your .env.schema flows into runtimeConfig as long as the values are in the process at runtime: this is automatic in dev, and in production it means launching through varlock run or using ssrInjectMode: 'auto-load'.
For non-prefixed keys, populate runtimeConfig explicitly in nuxt.config.ts from ENV (see Nuxt config timing below for config-time access).
We recommend ENV for your own code; the interop mostly matters for modules you do not control.
Nuxt’s own .env loading
Section titled “Nuxt’s own .env loading”The Nuxt CLI loads a single file named .env from the project root into process.env before any module runs. There is no multi-file cascade (no .env.local or .env.production), and there is no supported way to turn it off; the --dotenv flag only points it at a different single file.
The simple fix: don’t keep a file named .env at all. Varlock’s conventions already point this way, with your schema and defaults in .env.schema and local overrides in .env.local (plus env-specific files like .env.production). None of those filenames are touched by Nuxt’s loader, so varlock stays the single source of truth.
If you do keep a plain .env around, two details matter:
- Nuxt’s
.envvalues never override env vars that are already set in the process. If you launch throughvarlock run, varlock’s resolved values win. - Keys managed by varlock stay consistent, but any key varlock does not manage still lands in
process.env, soprocess.env.XandENV.Xcan diverge for it.
Nuxt config timing
Section titled “Nuxt config timing”Nuxt evaluates nuxt.config.ts before module setup runs. Because of that, ENV from varlock/env is not initialized there by the module itself.
If you need env values in nuxt.config.ts, import varlock/auto-load at the top of the config file. It loads and validates your env before the rest of the file evaluates, so you get the typed ENV object. This is for settings Nuxt or other modules only read at config time, like app.baseURL, vite/nitro options, or another module’s options:
import 'varlock/auto-load';import { ENV } from 'varlock/env';
export default defineNuxtConfig({ modules: ['@varlock/nuxt-integration', '@nuxtjs/sitemap'], app: { baseURL: ENV.APP_BASE_PATH, }, site: { url: ENV.PUBLIC_SITE_URL, },})In dev, the module re-resolves your env at the start of every restart it triggers, so config-time values stay in sync when env files change (see Dev server behavior).
Within other scripts
Section titled “Within other scripts”You can use varlock run to inject resolved config into other scripts as regular env vars.
npm exec -- varlock run -- node ./script.jspnpm exec -- varlock run -- node ./script.jsbunx varlock run -- node ./script.jsvlx -- varlock run -- node ./script.jsyarn exec -- varlock run -- node ./script.jsType-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...Public dynamic env endpoint
Section titled “Public dynamic env endpoint”When the schema contains any public+dynamic keys, the module automatically injects a server route at /__varlock/public-env that serves them to the browser. If browser code needs one of these values, load it with loadPublicDynamicEnv() before reading ENV.KEY; this is rarely needed, but when it is, see Loading dynamic vars in the client.
You can control the injection through the module options:
export default defineNuxtConfig({ modules: ['@varlock/nuxt-integration'], varlock: { publicDynamicEndpoint: { path: '/api/public-env' }, // auto by default },})undefined(default): injected only when public+dynamic config items existtrue/false: force it on or off{ path }: custom route path; the module also points the client runtime at it, soloadPublicDynamicEnv()works without passingendpoint
Dev server behavior
Section titled “Dev server behavior”The module watches every env file varlock loads, including .env.schema, and restarts the dev server when one changes. This keeps everything in sync: inlined page values, server routes, the Nitro side, and values captured in nuxt.config.ts itself (the module re-resolves env at the start of every dev-server reload, before the config re-evaluates, including reloads triggered by editing nuxt.config.ts directly). Nuxt’s own watcher only reacts to a plain .env file.
If your config becomes invalid (a failed validation, a syntax error in a schema file), requests show an error page describing the problem, and the server recovers automatically once you fix and save the file.
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 using the @currentEnv root decorator. This is different from Nuxt’s default behavior, which relies on its own environment overrides.
# @currentEnv=$APP_ENV# ---# @type=enum(development, preview, production, test)APP_ENV=developmentThis will cause varlock to automatically load .env.development, .env.preview, etc., based on the value of APP_ENV at load time.
SSR injection modes
Section titled “SSR injection modes”When building a Nuxt application with server-side rendering, varlock injects initialization code into both the Vite SSR bundle and the Nitro server bundle. That init call is what makes ENV available to your server routes, and what installs log redaction and response leak prevention. The ssrInjectMode option controls where the resolved values come from:
'init-only'(default): initialize from env vars already present in the server process. Your deploy has to supply them, either by launching throughvarlock runor by setting them on the platform.'auto-load': the server loads varlock itself on startup. Use this for Node.js hosting where you runnode .output/server/index.mjsdirectly and your.envfiles ship alongside the build.'resolved-env': bake the resolved values into the build artifact. Use this on platforms that build and run in separate steps with no.envfiles at runtime, such as Vercel or Netlify. See encrypted deployments before shipping sensitive values this way.
export default defineNuxtConfig({ modules: ['@varlock/nuxt-integration'], varlock: { ssrInjectMode: 'auto-load', },})With the default 'init-only' mode, start the built server through varlock run:
{ "scripts": { "preview": "varlock run -- node .output/server/index.mjs" }}