Next.js: publicRuntimeConfig shouldn鈥檛 be undefined on build

Created on 1 Jul 2019  路  30Comments  路  Source: vercel/next.js

Bug report

Describe the bug

v8.1.1-canary.62 (or maybe 3aed76fad857b518a31675987e8c79f6f1afccde) (re-)introduces an issue with how I use (and recommend how to use) https://github.com/tusbar/next-runtime-dotenv.

To Reproduce

This breaks on build as publicRuntimeConfig is undefined at build time:

const {
  publicRuntimeConfig: {MY_API_URL}
} = getConfig()

Expected behavior

publicRuntimeConfig used to be {} at build time.

System information

  • OS: macOS
  • Version of Next.js: 8.1.1-canary.62

Additional context

This is not the first time this breaks, maybe it would be nice to add a test. It used to work fine with 8.0.4, it鈥檚 broken in 8.1.0 and used to work fine in canary before 8.1.1-canary.62. Broken in 9.0 as well.

bug

Most helpful comment

I reproduced this bug on 9.4.2

All 30 comments

@timneutkens could we have a consistent behavior for this? Thanks.

I'm using the built in method of using publicRuntimeConfig and not the above plugin and also receive the same error message on build

@cbarratt the plugin just pre-fills the runtime configuration with environment variables using dotenv.

Accessing the configuration is not different from the doc.

I also received the same error message on build and think I figured it out why. It has to do with Automatic static optimization introduced in Next 9, or 8.1.1-canary.62.

Following the documentation that states "This determination is made by whether or not the page has blocking data requirements through using getInitialProps." I ended up adding getInitialProps to every page in my application and the error went away.
Since there was none getInitialProps statement in some pages, during build it would consider this pages as static and does not expose runtime env configuration.

I'm seeing the same error on build, but am getting notices that Automatic static optimization is opt-out, so not sure if it's related @vitorhsb.

A few details to help debug:

  • Works with [email protected]
  • Fails with [email protected]
  • I'm using Apollo and have a custom getInitialProps in _app.js to renderDataFromTree. Not sure if that matters.
  • I'm also using withPlugins in my next.config.js file.
  • I'm manually calling require("dotenv").config(); in the top of my next.config.js file. (not using next-runtime-dotenv)

Build Output

Compiled successfully.

Warning: You have opted-out of Automatic Prerendering due to `getInitialProps` in `pages/_app`.
Read more: https://err.sh/next.js/opt-out-automatic-prerendering

> Build error occurred
{ TypeError: Cannot read property 'publicRuntimeConfig' of undefined
    at Object.ejXW (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:8637:28)
    at __webpack_require__ (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:23:31)
    at Object.nrlr (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:10516:13)
    at __webpack_require__ (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:23:31)
    at Object.Jrf2 (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:4298:14)
    at __webpack_require__ (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:23:31)
    at Module.oSZ7 (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:12982:12)
    at __webpack_require__ (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:23:31)
    at Object.12 (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:488:18)
    at __webpack_require__ (/Users/egdelwonk/dev/lp/frontend/build/server/static/yNatTl7kpzkSAKqWfkbLL/pages/journals/templates/destination/detail.js:23:31) type: 'TypeError', '$error': '$error' }
npm ERR! code ELIFECYCLE
npm ERR! errno 1

Hi, I'm not able to reproduce this error, could someone who is encountering this issue add a link to a repo or their next.config.js?

On a minimal project publicRuntimeConfig and serverRuntimeConfig are both being populated during build. We also do have a test for this here

Screen Shot 2019-07-08 at 1 51 49 PM

Hey, I am also seeing this error on production builds after upgrading to 9.0.0. It worked fine on 8.1.0.

Doing the following:

import getConfig from 'next/config'
console.log(getConfig())

seems to randomly switch between printing the config and undefined during the build.

Here is our next.config.js:

const withCss = require('@zeit/next-css')
const withSass = require('@zeit/next-sass')
const withImages = require('next-images')

const isDev = process.env.NODE_ENV === 'dev'

const withSassConfig = {
  cssModules: true,
  cssLoaderOptions: {
    modules: true,
    importLoaders: 1,
    localIdentName: isDev ? '[name]__[local]--[hash:base64:5]' : '[sha1:hash:hex:4]',
  },
}

const nextConfig = {
  publicRuntimeConfig: {
    // config vars..
  },
}

const config = {
  ...withSassConfig,
  ...nextConfig,
  webpack: webpackConfig => webpackConfig,
}

module.exports = withCss(withSass(withImages(config)))

Hi @timdavish, I'm still not able reproduce. It would help a lot if someone could create a minimal repo that shows this occurring 馃憤

Tested next.config.js:

const withCss = require('@zeit/next-css')
const withSass = require('@zeit/next-sass')
const withImages = require('next-images')

const isDev = process.env.NODE_ENV === 'dev'

const withSassConfig = {
  cssModules: true,
  cssLoaderOptions: {
    modules: true,
    importLoaders: 1,
    localIdentName: isDev ? '[name]__[local]--[hash:base64:5]' : '[sha1:hash:hex:4]',
  },
}

const nextConfig = {
  serverRuntimeConfig: {
    secret: 'pass'
  },
  publicRuntimeConfig: {
    hello: 'world'
  },
}

const config = {
  ...withSassConfig,
  ...nextConfig,
  webpack: webpackConfig => webpackConfig,
}

module.exports = withCss(withSass(withImages(config)))

pages/index.js

import getConfig from 'next/config'

console.log(getConfig())

export default () => <p>Hello world!!</p>

Screen Shot 2019-07-08 at 3 57 29 PM

@timdavish I have the impression that there's some sort of race condition. When I run the build inside a docker container it fails, but when I run on my Mac directly it builds just fine

I'm running into this problem as well. My project is large so a minimal repro is going to be tricky, but I suspect that this is a race condition so it may not even be possible. Running the same command (NODE_ENV=production next build) in two environments gets the same error on different pages in the app.

Early 2015 Macbook Pro (3.1 GHz Intel Core i7) I get the following:

{ TypeError: Cannot destructure property `publicRuntimeConfig` of 'undefined' or 'null'.
    at Object.tpE7 (/Users/ericbaer/dev/work/lnc/constellation/web/.next/server/static/hAFgtwbpTC55qnszuIR1i/pages/inventory.js:4011:55)

Netlify CI Environment (node10 Docker image)

TypeError: Cannot destructure property `publicRuntimeConfig` of 'undefined' or 'null'.
5:12:27 PM:     at Object.tpE7 (/opt/build/repo/web/.next/server/static/RVuMYAXq8hm_vjh7oqK4-/pages/order-acknowledgment.js:3740:55)

A few notable things about my project:

  • My project is large. There are ~90 components exported in the build.manifest, static 87Mb output dir.
  • My config is unique since I do static export with dynamic routes deployed to a CDN. You can see a description and minimal repo explaining the technique in https://github.com/zeit/next.js/pull/6963
const { set, uniq } = require(`lodash/fp`);
const { BundleAnalyzerPlugin } = require(`webpack-bundle-analyzer`);
const redirects = require(`./redirects`);

const { ANALYZE, API_ROOT } = process.env;

module.exports = {
  publicRuntimeConfig: {
    API_ROOT: API_ROOT || `http://localhost:8000/api/`,
  },
  exportPathMap: function() {
    const staticPages = uniq(redirects.map(redirect => redirect.staticPage));
    const staticExports = staticPages.reduce(
      (memo, page) => set(page, { page }, memo),
      {}
    );

    return {
      "/": { page: `/` },
      "/auth/signed-in": { page: `/auth/signed-in` },
      ...staticExports
    };
  },
  webpack: function(config, options) {
    if (!options.dev) {
      config.devtool = `nosources-source-map`;

      // Enable sourceMaps in UglifyJs
      for (const plugin of config.plugins) {
        if (plugin[`constructor`][`name`] === `UglifyJsPlugin`) {
          plugin.options.sourceMap = true;
          break;
        }
      }
    }

    return config;
  }
};

It looks like when the require cache is cleared in static-checker.ts it is wiping out next/config (which uses the require cache to persist the configuration afaik). I disabled the cache purge for next/config.js in static-checker.ts and the error no longer appears.

As far as how to replicate the issue... I'm not entirely sure. It seems like you'd need a decently large number of pages to compile (I have ~30) with a decently large compile time per page (largish component graph? Largish number of packages included on the page? Large kb?) to see the error. With these changes to static-checker below, the error goes away:

import { isPageStatic } from './utils'

export default function worker(
  options: any,
  callback: (err: Error | null, data?: any) => void
) {
  try {
    const { serverBundle, runtimeEnvConfig } = options || ({} as any)
    const result = isPageStatic(serverBundle, runtimeEnvConfig)

    // clear require.cache to prevent running out of memory
    // since the cache is persisted by default
    Object.keys(require.cache).map(modId => {
      // Disable cache purge for next/config module to persist configuration
      if (modId.endsWith("node_modules/next/config.js")) {
        return;
      }
      const mod = require.cache[modId]
      delete require.cache[modId]
      if (mod.parent) {
        const idx = mod.parent.children.indexOf(mod)
        mod.parent.children.splice(idx, 1)
      }
    })

    callback(null, result)
  } catch (error) {
    callback(error)
  }
}

cc @ijjk

@egdelwonk thanks for the additional details, this makes more sense. I have submitted a PR addressing the problem here

Looks good, thanks @ijjk.

@egdelwonk Where does that code go to? Does one need to get inside node_modules and update code?

Will it be fixed officially or we have to hack like so?

@ryandcsg looks like @ijjk made a PR to fix it (slightly different approach, but it should work) and i'd imagine it's coming in the 9.0.1 release. It's on the canary branch, but hasn't made it to a 9.0.x canary release yet.

Hey! I'll release a canary now so you can use this fix.

Can confirm that the canary release fixes the issue. Thanks everyone!

I reproduced this bug on 9.4.2

Hey @ijjk - it seems like this bug has returned, as per the 16 upvotes there ^

No reproduction was given on the new comment so that makes it hard to look into.

I've tried v9.5.2 which should include #15777 and we're still getting undefined back from any getConfig call it seems.

The sentry example also experiences this error, due to the setup in the _app.tsx file.

import * as Sentry from '@sentry/node'
import { RewriteFrames } from '@sentry/integrations'
import getConfig from 'next/config'

if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
  const config = getConfig()
  const distDir = `${config.serverRuntimeConfig.rootDir}/.next`
  Sentry.init({
    enabled: process.env.NODE_ENV === 'production',
    integrations: [
      new RewriteFrames({
        iteratee: (frame: any) => {
          frame.filename = frame.filename.replace(distDir, 'app:///_next')
          return frame
        },
      }),
    ],
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  })
}

having the same error as above, getConfig no longer works for some reason, and is returning undefined, which breaks sentry configs

I am having the same issue on nextjs 9.5.3 publicRuntimeConfig is not returning custom variables, but only NODE_ENV :(

We are having the same issue that when running in a docker container, the publicRuntimeConfig does not appear to be set from runtime env vars. I've confirmed that they are set in the container and available on process.env when the app is running, and they are available server side, but not client side. We are not using any static pages, but we are using getServerSideProps.

I ran into the same issue in v9.5.3 and ended up downgrading to v9.0.2 which for now suits my needs.

Edit: v9.4.0 (and v9.1.0 and v9.2.0 and v9.3.0) also works fine for me. v9.5.0 doesn't and as mentioned above neither does v9.5.3.

Race condition seems to make sense here, if I follow the value of publicRuntimeConfig while building it changes from an empty object to undefined at some point and breaks the build.

I also ran into this issue and in my case it was introduced by this change in the 9.4.3 release.

Since this change all undefined properties of publicRuntimeConfig are removed at build time, and if that happens for all the properties then publicRuntimeConfig is seen as {} which is in turn filtered out.

A workaround can be to add at least one property to publicRuntimeConfig that is defined at build time, could be foo:'bar'.

In my case though the real problem was that I was not using getInitialProps as documented here.

For anyone else running into this, I ended up building the Next.js app when running my docker container (instead of when building the image) in order to avoid having to use the publicRuntimeConfig. I now just use standard build time env vars. It adds startup time, but solves this issue

I'm running into this issue in my Sentry config in [email protected], following the Sentry example mentioned by @ColeTownsend here.

Looking at the examples repo, it appears with-sentry was updated 7 hours ago. It has a different setup altogether:

// utils/sentry
import * as Sentry from '@sentry/node'
import { RewriteFrames } from '@sentry/integrations'

export const init = () => {
  if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
    const integrations = []
    if (
      process.env.NEXT_IS_SERVER === 'true' &&
      process.env.NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR
    ) {
      // For Node.js, rewrite Error.stack to use relative paths, so that source
      // maps starting with ~/_next map to files in Error.stack with path
      // app:///_next
      integrations.push(
        new RewriteFrames({
          iteratee: (frame) => {
            frame.filename = frame.filename.replace(
              process.env.NEXT_PUBLIC_SENTRY_SERVER_ROOT_DIR,
              'app:///'
            )
            frame.filename = frame.filename.replace('.next', '_next')
            return frame
          },
        })
      )
    }

    Sentry.init({
      enabled: process.env.NODE_ENV === 'production',
      integrations,
      dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
      release: process.env.NEXT_PUBLIC_COMMIT_SHA,
    })
  }
}
// pages/_app.tsx

import { init } from '../utils/sentry'

init()

export default function App({ Component, pageProps, err }) {
  // Workaround for https://github.com/vercel/next.js/issues/8592
  return <Component {...pageProps} err={err} />
}

Changing my _app.tsx file to use the new Sentry setup seems to have fixed this for me (given that getConfig() returns undefined and it's no longer used.)

Was this page helpful?
5 / 5 - 1 ratings

Related issues

swrdfish picture swrdfish  路  3Comments

DvirSh picture DvirSh  路  3Comments

knipferrc picture knipferrc  路  3Comments

jesselee34 picture jesselee34  路  3Comments

sospedra picture sospedra  路  3Comments