Learn Nuxt Icon

Nuxt runtimeConfig and your .env

In most of today's modern web development you will rarely see a project that does not require secrets or environment config. Whether it's an API key that your project uses on the backend or a variable that should be dynamic based on the developer's environment. Today we are going to look at how you can make this experience seemless with Nuxt.

The runtimeConfig option and global composable

In the previous article, we spoke about auto-imports. useRuntimeConfig is one of those identifiers that you can use throughout your app (server or client). It is a composable - which we haven't looked at yet, but for now just look at it as a global function with some shared state and in this case we define the state in our project's nuxt.config.ts. Let's look at an example on a fresh project:

~/my-nuxt-app/nuxt.config.ts


export default defineNuxtConfig({
  runtimeConfig: {
    secretApiKey: 'my-api-key', // Hmm something is wrong here...
    public: {
      clientApiKey: 'client-api-key'
    }
  }
})

In the config above, we need to think of the structure in two mental models. The public object is read by Nuxt as variables that are exposed to the server and the client. Ideally this is where you will put config items that are safe to expose in the browser, this could be client API keys for example. Everything outside of public will only be available in the server build of your app.

The runtimeConfig can be defined like this, but in reality we don't want to hard-code secrets into our source code and check it into our version control. We'll look at how to secure this in a bit with a .env file.

Using the runtimeConfig

~/my-nuxt-app/app.vue


<script lang="ts" setup>
const config = useRuntimeConfig();

console.log('My public API key: ', config.public.clientApiKey);

if (process.server) {
  console.log('My secret API key: ', config.secretApiKey);
}
</script>

<template>
  <!-- Safe to expose -->
  <p>{{ config.public.clientApiKey }}</p>

  <!-- ⚠️⚠️⚠️ do not render your secrets -->
  <p>{{ config.secretApiKey }}</p>
</template>

Most of the time, you won't be using non-public runtimeConfig in your markup, but instead in your server routes:

~/my-nuxt-app/server/api/index.get.ts


export default defineEventHandler(() => {
  const { secretApiKey } = useRuntimeConfig();
  const response = await $fetch('https://some.internal.api', {
    headers: { 'Authorization': `Bearer ${secretApiKey}` }
  });
  return response;
})

The code above may or may not be familiar to you, but don't worry if you're not sure what's happening. We'll be diving into our first project soon!

Using a .env file to populate our runtimeConfig

Nuxt provides a very friendly way to map our .env to our runtimeConfig. First we start by defining our runtimeConfig structure with empty values:

~/my-nuxt-app/nuxt.config.ts


export default defineNuxtConfig({
  runtimeConfig: {
    secretApiKey: '',
    public: {
      clientApiKey: ''
    }
  }
})

Now in our .env file in our project root, we can use special prefixed variables that Nuxt will automatically read into our config:


NUXT_SECRET_API_KEY=my-api-key
NUXT_PUBLIC_CLIENT_API_KEY=client-api-key

Nuxt will look at all the variables that start with NUXT and replace the uppercase text with camel case and populate it within your runtimeConfig, e.g. NUXT_CMS_API_KEY cmsApiKey.

Using a different .env file

If you want to use a different .env file in e.g. development, you can update your nuxi dev script:


{
  "scripts": {
    "dev": "nuxt dev --dotenv .env.local"
  }
}

Security best practices ⚠️

To keep your server side variables safe, you should not expose them by for example using them useState (which we will still look at) or rendering them in your templates with {{ }}. A good general rule of thumb is to only use your non-public runtimeConfig variables in the server-side of your app. E.g.

  • api routes
  • useAsyncData and other functions that run on the server (Be aware that the secret will only be used if the call is made on the server, e.g. a hard load of the page. It will be undefined when routing client side).