Nuxt Environment Overrides: Clean Config for Every Environment

Learn how to manage multiple environment configs in Nuxt with ease. From development to staging and production, streamline your setup with powerful Nuxt tools.

Michael Thiessen
Nuxt 3

The Mastering Nuxt FullStack Unleashed Course is here!

Get notified when we release new tutorials, lessons, and other expert Nuxt content.

Click here to view course

Having different configurations in each environment is critical for any production web app, and Nuxt gives you some powerful tools to make this super easy.

Alongside your development and production environments, you also may have a staging or testing environment. Each of these is configured slightly different, and managing those configurations can sometimes be really annoying.

But Nuxt makes this super simple with environment overrides.

Here’s a quick example of what this looks like:

export default defineNuxtConfig({
    modules: ["@nuxt/ui"],
  $development: {
      modules: ["@nuxtjs/html-validator"]
  }
});

Nuxt environment overrides let you define development, staging, testing, or production config blocks in nuxt.config.ts. These overrides merge with your base config to keep your config file clean and easy to read.

Let’s dig in deeper into how these environment overrides in Nuxt work, and how you can use them in your own apps.

The problem with .env files

A typical way to provide different configurations in each environment is through the use of different environment variables. You might already be using a set up with a bunch of these files with different names for each environment:

.env             # Defaults
.env.local       # Local development
.env.staging     # Staging environment
.env.production  # Production environment
.env.example     # A template file (not ever used directly)

This is great for all of the things you’ve set up in your runtimeConfig. But in a typical Nuxt app we have a lot more stuff we want to configure!

If you wanted to run HTML validation using the HTML Validator module, you’d only want that done in development. You may set up your nuxt.config.ts to do something like this:

export default defineNuxtConfig({
  modules: process.env.NODE_ENV === 'development'
      ? ["@nuxtjs/html-validator", "@nuxt/ui"],
      : ["@nuxt/ui"]
});

Here, we check NODE_ENV so we only add the @nuxtjs/html-validator module during development.

While this works, it’s not great. It’s very verbose, and hard to read.

Luckily, Nuxt has environment overrides that make this a lot easier to work with!

What are Nuxt Environment Overrides?

If we use environment overrides instead, we can rewrite the above example to this:

export default defineNuxtConfig({
    modules: ["@nuxt/ui"],
  $development: {
      modules: ["@nuxtjs/html-validator"]
  }
});

So much cleaner, so much easier to read. It’s very clear what’s going on here!

We don’t need to repeat ourselves for parts of the configuration that are shared across all environments either. The non-overridden part is deeply merged with the environment overrides, so here we only need to specify @nuxt/ui a single time.

We can define environment overrides for any environment we want, including development, staging, testing, or production:

export default defineNuxtConfig({
    // The shared config goes here
  $development: {
      // Development only
  },
  $staging: {
      // Staging only
  },
  $testing: {
      // Testing only
  },
  $production: {
      // Production only
  },
});

And since Nuxt is just matching based on what process.env.NODE_ENV is set to, we can use whatever values we want:

export default defineNuxtConfig({
  $michael: {
      // A special Michael mode
  },
});

You can also group all of your environment overrides together using the $env key:

export default defineNuxtConfig({
    // The shared config goes here
    $env: {
      development: {
          // Development only
      },
      staging: {
          // Staging only
      },
      testing: {
          // Testing only
      },
      production: {
          // Production only
      },
      michael: {
          // A special Michael mode
        }
    }
});

It’s all powered by c12

Like most things in the Nuxt ecosystem, it’s powered by a small unjs library called c12 (which itself uses a bunch of unjs libraries). It’s also used by Nitro, Prisma, and a bunch of other great OSS libraries.

It’s an extremely flexible and versatile configuration loader, that does a bunch of really cool things:

  • Handles .js/.ts/.mjs/.cjs/.mts/.cts/.json and .jsonc/.json5/.yaml/.yml/.toml
  • Supports .config/ dirs, .rc files, .env files, and reads from package.json.(github.com)
  • Merges sources with defu (another unjs library), following this priority:
    1. overrides
    2. CWD config
    3. CWD RC
    4. global RC
    5. package.json
    6. defaults
    7. extended layers
  • Supports extending from local or Git sources, environment‑specific overrides, hot‑reload/HMR, and programmatic config edits

This means that you can use it in any JS tool where you need smart configuration!

Wrapping Up

Nuxt’s environment overrides, backed by the powerful c12 loader, make configuring development, staging, testing, and production environments a breeze.

With clean, environment-specific blocks like $development or $env, you can avoid clunky process.env.NODE_ENV conditionals and deeply merge configs effortlessly.

And because it’s powered by c12, you also get flexible formats, smart merging, and support for config layers and hot-reloading, all without the usual env-file headache.

Michael Thiessen
Michael is a passionate full time Vue.js and Nuxt.js educator. His weekly newsletter is sent to over 11,000 Vue developers, and he has written over a hundred articles for his blog and VueSchool.

Follow MasteringNuxt on