6

I apologize if this question has been answered before, but I'm banging my head against the wall here with something that should be simple ....

My project is running in server mode. We need to be able to pass environment variables in at runtime and have them available on both server- and client-side

I'm using the publicRuntimeConfig approach as described in the docs and I haven't done anything out of the ordinary (code to follow)

The problem is when running in dev mode (yarn dev) everything works as expected, but when I build the project and dockerize it (or move it to kubernetes for deployment), it doesn't work correctly.

Here's the code: next.config.js

const nextConfig = {
  publicRuntimeConfig: {
    PARAM_A: process.env.PARAM_A || "defaultA",
    PARAM_B: process.env.PARAM_B || "defaultB"
  }
};

module.exports = nextConfig;

_app.tsx

import React from "react";
import App, { AppContext } from "next/app";
import Head from "next/head";
import { Menu } from "../src/components/menu";


class CustomApp extends App {
  static async getInitialProps({ Component, ctx }: AppContext) {
    let pageProps = {};

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
      console.log("[_app.tsx] pageProps", pageProps);
    }

    return { pageProps };
  }

  render() {
    const { Component, pageProps } = this.props;

    return (
      <>
        <Head>
          <title>Testing Area</title>
        </Head>
        <Menu />
        <Component {...pageProps} />
      </>
    );
  }
}

export default CustomApp;

index.tsx, otherpage.tsx and help.tsx are identical apart from their wording, so I'll only put down index.tsx to save space:

import React, { Component } from "react";
import getConfig from "next/config";
import { NextPageContext } from "next";

class Index extends Component {
  static async getInitialProps(ctx: NextPageContext) {
    const nextConfig = getConfig();
    const clientConfig = (nextConfig && nextConfig.publicRuntimeConfig) || {};
    const settings =  Object.keys(process.env).length > 1 ? process.env : clientConfig;
    console.log("[INDEX PAGE] - nextConfig", nextConfig);
    console.log("[INDEX PAGE] - clientConfig", nextConfig.publicRuntimeConfig);
    console.log("[INDEX PAGE] - settings", settings);
    console.log("[INDEX PAGE] - process.env", process.env);

    return { settings };
  }

  render() {
    return <div>INDEX Page</div>;
  }
}

export default Index;

When I run yarn dev, the output in the browser dev tools for this line console.log("[INDEX PAGE] - nextConfig", nextConfig); is as expected (note PARAM_A and PARAM_B):

[INDEX PAGE] - nextConfig 
{serverRuntimeConfig: {…}, publicRuntimeConfig: {…}}
serverRuntimeConfig: {}
publicRuntimeConfig:
PARAM_A: "mycustomAvalue"
PARAM_B: "mycustomBvalue"
__proto__: Object
__proto__: Object

When I dockerize the project and provide the env variables, the output is as follows for all pages and nothing gets passed from server-side to client-side for publicRuntimeConfig:

[INDEX PAGE] - nextConfig 
{serverRuntimeConfig: {…}, publicRuntimeConfig: {…}}
serverRuntimeConfig: {}
publicRuntimeConfig: {}
__proto__: Object

The first page gets the values (because the getInitialProps is executed in _app.tsx) but whenever I navigate to any other page (Using next/link) I lose the variables. I need these values available across my app in all the pages. Please tell me I'm missing something obvious.

Techedemic
  • 203
  • 3
  • 9
  • How are you passing the env vars to your container? Can you post your Dockerfile? – Steve Holgado Jan 24 '20 at 08:38
  • at the moment using a simple `env.list` file and the command `docker run -p 3001:3000 --env-file ./env.list -t prj-changed:latest` – Techedemic Jan 24 '20 at 09:22
  • And how to you pass env vars when you build your image? – Steve Holgado Jan 24 '20 at 09:27
  • I don't pass them. I want to do it at 'runtime' only because I need to be able to fire up the same image for different environments,e.g. dev, staging, beta, production – Techedemic Jan 24 '20 at 09:28
  • The `publicRuntimeConfig` is going to be bundled when you build your app so the client-side code will no longer have access to your env variables. – Steve Holgado Jan 24 '20 at 09:40
  • As stated in the answer, nextjs environment variables for the client side need to be available at build time, this is because nextjs does an in place replacement of the variables to their values at build time. To allow access to environment variables at runtime you need to create a server side end point in the router which the client can call to retreive the value. – James Nov 05 '21 at 09:03

1 Answers1

6

The publicRuntimeConfig is going to be bundled when you build your app.

Your app build happens when you build your Docker image. However, your environment variables are not available at this time.

When you start your container, you supply your environment variables so your server-side code can use them, but your client-side code cannot.


One solution would be to build your app on container start, as the build would then have access to the env variables that you supply. I would not really recommend this approach though.

Another solution would be to use Docker build args to set your environment variables at build time:

Dockerfile

ARG PARAM_A
ARG PARAM_B

ENV PARAM_A=$PARAM_A
ENV PARAM_B=$PARAM_B

# ...

Then pass the env variables as build args:

docker build --build-arg PARAM_A=something --build-arg PARAM_B=something ...

This allows you to pass different build args for each environment.

However, this does mean that you have a separate image for each of your environments.

I hope this helps.

Steve Holgado
  • 11,508
  • 3
  • 24
  • 32