Live Environment Vars in Isomorphic React
Asked Answered
I

3

6

I've built an isomorphic React app based loosely on the starter kit in this repo. It uses webpack to build the production code.

Thing is, I need to expose the value of a few environment variables on the server to the client code in the browser without rebuilding the production code. I want to be able to change the value of the env vars, and have it the effect the client on next page refresh, without having to rebuild anything. And I don't want to complicate testing in order to do it.

I've found a few solutions, none of which are great:

  1. Use the DefinePlugin for webpack to hard-code the value of certain environment variables into the production code. Similar to what's outlined here
  2. Build an API just to pull the env variables into the client.
  3. Write a special .js file that is outside the webpack system. This file would be templated so that it's modified before it's served to the client. Probably requiring that the env variable values are stored in special global variables on 'window' or something.

Problems with these approaches:

  1. REJECTED. This just doesn't do what I want. If I change the value of the env variable, I need to rebuild the code.
  2. Unnecessarily complicated. I wouldn't need this API for anything else. A whole API just to serve 2 or 3 values that rarely change? Complexity would be needed to ensure the values were pulled from the API ASAP on load.
  3. Closest yet, but kinda gross. I don't really want to go outside the webpack/React/Flux system if I can avoid it. Creating special global variables on the window object would work, but would introduce complexity for testing components/stores/actions that use those global variables.

I've done both 2 and 3 in the past and never was really intellectually satisfied by those solutions.

Any suggestions? Seems like this should be a common/solved problem. Maybe I'm just overthinking it and 3 is the way to go.

Infelicity answered 25/9, 2015 at 2:28 Comment(1)
3 seems like the best option.Reichenberg
I
0

So the solution that I came up with is fairly simple. I just write a one-liner of javascript to save the value to local storage inside a script tag. Then read that local storage from my flux store when the app starts.

This is the relevant tag added to the index.html:

<script type="text/javascript"><%= config %></script>

This is the string that I splice into index.html using templating, before serving it:

let configJs = 'localStorage.setItem(\'ADMIN_API\', JSON.stringify(\'' + process.env.ADMIN_API + '/v3/' + '\'));';
const html = template({config: configJs});
res.status(200).send(html);

Then I read it with this, once the app starts:

import ls from 'local-storage';
....
let api = ls.get('ADMIN_API');
Infelicity answered 28/9, 2015 at 17:7 Comment(0)
A
2

The boiler plate use express, you can expose server env variable to the client using express' local.

var serverVariable = 'this is a server variable'
app.get('/somelink', function (req, res, next) {
    res.locals.data = {
      FluxStore: { serverlocal: serverVariable  }
   }
   next()
})

You then pass the local either through React.renderToString which will be pick by FluxStore on the client. The other method, you can use data fetching api like falcor which can be pick by client side Action Store through falcor-http-datasource, you don't need express local for falcor you build it with falcor-express and falcor-router

Anthropometry answered 25/9, 2015 at 12:48 Comment(0)
I
0

So the solution that I came up with is fairly simple. I just write a one-liner of javascript to save the value to local storage inside a script tag. Then read that local storage from my flux store when the app starts.

This is the relevant tag added to the index.html:

<script type="text/javascript"><%= config %></script>

This is the string that I splice into index.html using templating, before serving it:

let configJs = 'localStorage.setItem(\'ADMIN_API\', JSON.stringify(\'' + process.env.ADMIN_API + '/v3/' + '\'));';
const html = template({config: configJs});
res.status(200).send(html);

Then I read it with this, once the app starts:

import ls from 'local-storage';
....
let api = ls.get('ADMIN_API');
Infelicity answered 28/9, 2015 at 17:7 Comment(0)
A
0

This uses a window global variable in window to pass the values but give you a universal interface to access the values on the browser and node.

In publicEnv.js:

// Env variable to push to the client. Careful on what you put here!
const publicEnv = [
  'API_URL',
  'FACEBOOK_APP_ID',
  'GA_ID'
];

// These 2 lines make sure we pick the value in the right place on node and the browser
const isBrowser = typeof window !== 'undefined';
const base = (isBrowser ? window.__ENV__ : process.env) || {};

const env = {};
for (const v of publicEnv) {
  env[v] = base[v];
}
export default env;

In the HTML template file of the page I have:

import publicEnv from 'publicEnv.js';

...

<script>
  window.__ENV__ = ${stringify(publicEnv)};

  // Other things you need here...
  window.__INITIAL_STATE__ = ${stringify(initialState)};
</script>

So now I can get the values of the env variables on both frontend and backend with:

import publicEnv from 'publicEnv.js';

...

console.log("Google Analytic code is", publicEnv.GA_ID);

I hope it can help.

Absorbefacient answered 20/1, 2017 at 0:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.