Skip to content

Nuxt

While Nuxt has runtimeConfig to help with environment variables, we think Varlock has more to offer:

  • Your .env.schema is not tied to JavaScript, and is a better place to store this schema info versus your nuxt.config.* file
  • Facilitates loading and composing multiple .env files
  • 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.

  1. Install varlock and the Nuxt module

    Terminal window
    npm install @varlock/nuxt-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. Enable the Nuxt module

    Add @varlock/nuxt-integration to your nuxt.config.ts modules array:

    nuxt.config.ts
    export default defineNuxtConfig({
    modules: ['@varlock/nuxt-integration'],
    })

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:

server/api/example.ts
import { ENV } from 'varlock/env';
console.log(process.env.SOMEVAR); // 🆗 still works
console.log(ENV.SOMEVAR); // ✨ recommended
  • 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

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.

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 .env values never override env vars that are already set in the process. If you launch through varlock run, varlock’s resolved values win.
  • Keys managed by varlock stay consistent, but any key varlock does not manage still lands in process.env, so process.env.X and ENV.X can diverge for it.

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:

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

You can use varlock run to inject resolved config into other scripts as regular env vars.

Terminal window
npm exec -- varlock run -- node ./script.js

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.

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

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:

nuxt.config.ts
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 exist
  • true / false: force it on or off
  • { path }: custom route path; the module also points the client runtime at it, so loadPublicDynamicEnv() works without passing endpoint

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.


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.

.env.schema
# @currentEnv=$APP_ENV
# ---
# @type=enum(development, preview, production, test)
APP_ENV=development

This will cause varlock to automatically load .env.development, .env.preview, etc., based on the value of APP_ENV at load time.


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 through varlock run or by setting them on the platform.
  • 'auto-load': the server loads varlock itself on startup. Use this for Node.js hosting where you run node .output/server/index.mjs directly and your .env files 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 .env files at runtime, such as Vercel or Netlify. See encrypted deployments before shipping sensitive values this way.
nuxt.config.ts
export default defineNuxtConfig({
modules: ['@varlock/nuxt-integration'],
varlock: {
ssrInjectMode: 'auto-load',
},
})

With the default 'init-only' mode, start the built server through varlock run:

package.json
{
"scripts": {
"preview": "varlock run -- node .output/server/index.mjs"
}
}