20

I have a nextjs (not sure if it has any relevance or does it apply to nodejs as a whole) project in which I would like to access a value from process.env using a dynamic key:

const myKey = 'MY_KEY'
console.log(process.env[myKey]) //undefined

For reference, I have tried:

console.log(process.env['MY_KEY']) // gives value under MY_KEY
console.log(process.env.MY_KEY) // works too
6
  • 5
    Works for me. Can you share an executable snippet that showcases this behavior?
    – Mureinik
    Commented Oct 1, 2020 at 9:54
  • 1
    I am having this same issue. myKey always returns undefined. Commented Nov 19, 2020 at 17:21
  • Seeing the same. Ended up conditionally assigning the value to a var, but really eager to understand how to solve this. Commented Mar 14, 2021 at 14:58
  • can you share simple project?
    – Wang Liang
    Commented Mar 14, 2021 at 15:06
  • 1
    @JasperKennis Are you correctly exposing the environment variables to the browser using the NEXT_PUBLIC_ prefix? Commented Mar 14, 2021 at 15:14

2 Answers 2

33
+200

First of all, one thing to note, if you want to use env variables on the client then you need to prefix them with NEXT_PUBLIC_, or use the older way of exposing them to the client inside next.config.js.

As for the question, this is due to the nature of how Webpack DefinePlugin works which I believe NextJs uses internally. It does direct text replacement at build time, in simpler words it just looks for process.env.MY_KEY string and replaces it with value. But if you destructure it then the plugin just can't find process.env.MY_KEY string anymore and you get nothing (process.env object is generated anyway, but it will be empty).

Although this is only true for client side code, because for server side Next actually uses a real process.env object and destructuring will work there.

For example, if we have NEXT_PUBLIC_MY_KEY=somevalue and we log this somewhere in the code:

  const key = 'NEXT_PUBLIC_MY_KEY';

  console.log(process.env.NEXT_PUBLIC_MY_KEY);
  console.log(process.env['NEXT_PUBLIC_MY_KEY']);
  console.log(process.env[key]);
  console.log(process.env);

On client side you will get:

somevalue
somevalue
undefined
{} // empty object

And on server side you will get:

somevalue
somevalue
somevalue
{ ... } // object with all available env values, even system ones

There is a little bit of info about it in older env docs.

Workaround

You could probably use Runtime Configuration, but it has it's own limitations, for example, I think the page should be dynamic (should use getInitialProps or getServerSideProps) to work.

// next.config.js
module.exports = {
  publicRuntimeConfig: {
    myEnv: 'somevalue',
    // or actually use process.env variables, they are all available here
    myEnv2: process.env.MY_ENV
  },
};

import getConfig from 'next/config';

const key = 'myEnv';

getConfig().publicRuntimeConfig[key] // 'somevalue' 

Or just pass your variables through getServerSideProps like the other answer mentioned.

EDIT: I've actually just tested publicRuntimeConfig and it works even on static pages, at least if you are using next start. Not sure why docs says that the page should have getServerSideProps. So this might be the solution in the end.

0
3

As someone else has already correctly mentioned, this problem occurs because that's how the Webpack DefinePlugin works.

Solution

Extending the webpack config. You can filter out all environment variables, starting with NEXT_PUBLIC_ and add them with the DefinePlugin webpack plugin to the process.env object. This way you can call them dynamically in the browser.

next.config.js

module.exports = {
  webpack: (config, { webpack, isServer }) => {
    const envs = {};
    
    Object.keys(process.env).forEach(env => {
      if (env.startsWith('NEXT_PUBLIC_')) {
        envs[env] = process.env[env];
      }
    })
    
    if (!isServer) {
      config.plugins.push(new webpack.DefinePlugin({
        'process.env': JSON.stringify(envs),
      }))
    }

    return config
  },
}

your page

import { useEffect } from 'react';

const keys = ['FOO', 'NEXT_PUBLIC_FOO'];

export default function Home() {
  useEffect(() => {
    keys.forEach(key => console.log(process.env[key]));
  }, []);

  return (
    <>
    </>
  )
}

Workaround

This workaround adds all environment variables prefixed with NEXT_PUBLIC to become dynamically available in the frontend.

import { useEffect } from 'react';

const keys = ['FOO', 'NEXT_PUBLIC_FOO'];

export default function Home(props) {
  useEffect(() => {
    keys.forEach(key => console.log(props.envs[key]));
  }, []);

  return (
    <>
    </>
  )
}

export async function getStaticProps() {
  const envs = {};
  
  Object.keys(process.env).forEach(env => {
    if (env.startsWith('NEXT_PUBLIC_')) {
      envs[env] = process.env[env];
    }
  })

  return {
    props: {
      envs,
    },
  }
}

The important part is, that you use the getStaticProps method. Here you filter out all environment variables starting with NEXT_PUBLIC_ and add them to the envs property. This allows them to be dynamically usable in the frontend.

In this example, the value of NEXT_PUBLIC_FOO will log to the console, while the value of FOO is undefined.

3
  • Your solution with custom DefinePlugin will actually break server side process.env. You need to check for isServer at the very least.
    – Danila
    Commented Mar 14, 2021 at 16:31
  • Thanks for the heads-up. I've updated my answer accordingly and fixed this bug
    – Keimeno
    Commented Mar 14, 2021 at 16:45
  • How can you loop through process.env? For me it shows an empty object, even though the keys (when accessed not dynamically) are available
    – sir-haver
    Commented Oct 23, 2023 at 9:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.