Nuxt Runtime Config vs. Build-Time Environment Variables
AI generated
{ }
Nuxt 3 · Configuration · Security
Nuxt Runtime Config vs. Build-Time Environment Variables
Which configuration type is right, and where the security risks lie

runtimeConfig is built for values that can still change after the build, for example because the same Docker container runs in several environments with different API endpoints. The difference between public and private values directly determines whether a value ends up accidentally exposed as a secret in the client.

15 min read runtimeConfig · Nuxt 3 Docker & Secrets

1. Build Time and Runtime: Two Different Points in Time

In a classic frontend application, environment variables are usually baked directly into the JavaScript code during the build process. A value like process.env.API_URL gets replaced by the build tool with the actual string, and that string is then permanently fixed inside the shipped bundle, regardless of which environment the bundle actually ends up running in later.

This works fine as long as a separate build gets produced for every environment, such as staging and production. But as soon as the same, already-built Docker container is supposed to run in several environments with different configuration values, this approach falls short, since the value was already baked immutably into the code at build time and can no longer change at runtime.

2. The Concept Behind runtimeConfig

Nuxt solves this problem with runtimeConfig, a configuration structure whose values aren't baked in at build time but are instead read from the actual environment variables of the running process when the server starts. The same built container can therefore be launched in different environments with different values, without needing a new build.

runtimeConfig gets defined centrally in nuxt.config.ts, where the values stored there simultaneously serve as defaults and as a type definition. At runtime, Nuxt automatically overrides every value as soon as an appropriately named environment variable is set, which cleanly separates configuration from code.

3. Public vs. Private Runtime Config Values

Within runtimeConfig, Nuxt strictly distinguishes between top-level values, which are available only on the server, and values nested under the public key, which are accessible both on the server and in the client. This separation isn't just a convention, it's technically enforced by Nuxt, since only the public portion actually gets embedded in the JavaScript bundle shipped to the client.

In the example below, the database access key is available exclusively on the server, while the public API base URL is deliberately made available to the client too, via public, for instance for client-side $fetch calls made directly from the browser.


// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // server-side only
    databaseApiKey: '',

    public: {
      // available on both server and client
      apiBase: 'https://api.example.com',
    },
  },
});

// server/api/orders.get.ts
export default defineEventHandler((event) => {
  const config = useRuntimeConfig(event);
  // databaseApiKey is available here, but NOT in the client
  return $fetch(`${config.public.apiBase}/orders`, {
    headers: { Authorization: `Bearer ${config.databaseApiKey}` },
  });
});

4. Practical Case: Docker Containers Across Different Environments

In a typical Docker-based deployment, an image gets built exactly once and then rolled out unchanged across several environments such as staging, pre-production, and production. If API endpoints or feature flags were treated as build-time variables, a separate image would have to be built for every environment, which breaks the principle of building once and deploying an identical artifact everywhere.

With runtimeConfig, the image stays identical across all environments, and only the environment variables set when the container starts differ between environments. This matches the established principle of strictly separating configuration from code, and it also reduces the risk that different code gets tested and shipped across environments.

5. Naming Convention for Environment Variables

Nuxt automatically derives the name of the corresponding environment variable from the nested key path in runtimeConfig, using uppercase letters and underscores. A value at runtimeConfig.public.apiBase, for example, can be set through the variable NUXT_PUBLIC_API_BASE, while a private value like runtimeConfig.databaseApiKey corresponds to NUXT_DATABASE_API_KEY.

This automatic derivation significantly reduces boilerplate, since no manual mapping between environment variables and the configuration structure needs to be maintained. In more complex projects with many nested configuration values, it's still worth documenting the full list of resulting variable names, to avoid typos when setting the variables in the deployment pipeline.

6. When Build-Time Variables Are Still Necessary

Not every piece of configuration is a good fit for runtimeConfig. Values that influence the build output itself, such as which modules get enabled, which CSS file gets loaded, or whether certain code should be stripped from the bundle entirely, necessarily have to be known at build time, since they change the result of the build process itself rather than becoming relevant only after the server starts.

For such cases, classic build-time environment variables, read directly inside nuxt.config.ts via process.env, remain the right approach. The deciding factor is whether a value controls the behavior of the already-built application at runtime, in which case runtimeConfig applies, or whether it influences the build process itself, in which case a classic build-time variable is the right tool.

7. Security Implications of Exposed Secrets

The most common and most dangerous mistake when working with runtimeConfig is accidentally placing a sensitive value, such as an API key, a database password, or a private certificate, inside the public key. Since everything under public ends up unencrypted in the JavaScript bundle shipped to the browser, any visitor to the site can simply inspect that value through the browser's developer tools.

What makes this especially tricky is that such a mistake usually goes unnoticed during local development, since everything appears to work as expected, while the secret is already visible to every site visitor in the background. Regularly checking the actually shipped bundle, for example through the network panel of the browser's developer tools, along with a deliberate code review rule for every change to runtimeConfig, helps minimize this risk.

8. Best Practices for Working with runtimeConfig

As a basic rule: every value starts out as a private, server-side value at the top level of runtimeConfig, and only values that are demonstrably needed in the client get moved deliberately and explicitly into the public key. This defensive default stance prevents a value from getting marked public out of convenience when it never actually needed to be.

It's also worth automatically checking, as part of the deployment pipeline, whether all expected environment variables are actually set when the container starts, to prevent the application from running in production with empty or missing values from runtimeConfig. A missing but expected value should ideally cause the deployment process to fail early and visibly.

9. Conclusion: Deliberately Separate Configuration by Timing

The choice between runtimeConfig and classic build-time environment variables should always be based on whether a value controls the behavior of the finished build at runtime or influences the build process itself. For Docker-based deployments with an image built once and rolled out across several environments, runtimeConfig is generally the more fitting and flexible choice.

At the same time, runtimeConfig demands discipline in separating public from private values, since a single misplaced value is enough to permanently expose a secret to every site visitor. Anyone who consistently maintains this separation gains the flexibility of runtime configuration without taking on the associated security risks.

Aspect Build-Time Env Variable runtimeConfig
Resolved at During the build process When the server starts, at runtime
Change without rebuild Not possible Possible, via newly set env variables
Typical use Enabled modules, CSS selection API endpoints, feature flags, secrets
Docker fit One image per environment needed One image for all environments
Security risk Value lives in source code/build Risky only with wrong public placement

Mironsoft

Vue architecture, Composition API, and Nuxt performance

Vue applications that don't get more complicated with every feature?

We review existing Vue and Nuxt projects for unstructured composables, unnecessary reactivity, and bloated bundles, then build an architecture that absorbs new features without making the codebase harder to follow.

Architecture Review

Checking composables, state management, and component structure for maintainability.

Performance Audit

Systematically optimizing reactivity overhead, bundle size, and Nuxt rendering strategy.

Nuxt Integration

Building robust, type-safe SSR/SSG setup and API integration.

10. Summary

Runtime Config vs. Build-Time Env: The Essentials at a Glance

runtimeConfig

Values that can still change after the build, read from the environment

public key

The only values that actually reach the client bundle

Docker advantage

One image for all environments, configuration via env variables

Biggest risk

A secret accidentally placed in public instead of staying private

11. FAQ: Runtime Config vs. Build-Time Env: The Essentials at a Glance

1What's the core difference between runtimeConfig and process.env in nuxt.config.ts?
process.env inside nuxt.config.ts gets evaluated at build time and influences the build output itself. runtimeConfig values, on the other hand, are read from the current environment variables when the server starts and can change without a new build.
2Why does a value from the public key end up in the client bundle?
Nuxt deliberately embeds the entire contents of runtimeConfig.public into the JavaScript shipped to the browser, so client-side code, such as $fetch calls, can access those values. Everything outside public stays server-side only.
3How do I access runtimeConfig inside a component?
Through the useRuntimeConfig() composable. Inside a Vue component that runs both on the server and the client, you should only access values under public, since private values would be undefined in the client anyway.
4Can I set runtimeConfig values through Docker Compose?
Yes, it's enough to set the correspondingly named environment variables, such as NUXT_PUBLIC_API_BASE, in the environment block of Docker Compose or in a .env file loaded when the container starts.
5What happens if an expected environment variable is missing?
Nuxt falls back to the default value defined in nuxt.config.ts in that case. If both the environment variable and a sensible default are missing, the application should ideally flag the missing value clearly through its own startup check.
6Is runtimeConfig useful for statically generated (SSG) pages too?
For fully statically generated pages with no running server, runtimeConfig loses part of its usefulness, since there's no server startup left to re-read environment variables. For server-rendered or hybrid deployments, the approach remains fully effective.
7How do I find out which environment variable maps to which runtimeConfig value?
Nuxt automatically derives the variable name from the nested key path, in uppercase with underscores and the NUXT_ prefix. When in doubt, checking the official Nuxt documentation or logging the resolved configuration object during debugging helps.
8Can a value exist as both private and public at the same time?
Not directly under the same key, but it's possible to define two separate values with different content, such as a full, private API key and a public, restricted variant of the same service.
9How do I check after the fact whether a secret was accidentally exposed?
A direct look at the shipped JavaScript bundle in the browser, for example via developer tools, or searching the build output for known secret patterns, reliably shows whether a value actually ended up in the client.
10Do I need to use runtimeConfig for every tiny configuration value?
No, for values that never differ between environments and don't control application behavior, a fixed constant in the code is often perfectly sufficient. runtimeConfig pays off mainly for values that genuinely depend on the environment.