Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NextJS and environment variables - getting values to client side

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.

like image 274
Techedemic Avatar asked Oct 15 '22 07:10

Techedemic


1 Answers

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.

like image 100
Steve Holgado Avatar answered Nov 02 '22 23:11

Steve Holgado