David Gaskin
David Gaskin

Reputation: 586

Interpolate env vars client side in gatsby react app

I am using Gatsby as a Static Site Generator and using Netlify to deploy.

Netlify lets you set Environment Variables in its UI backend.

I've set a few env vars in the Netlify backend to be able to post subscribers to a mailing list.

DATA_NO = 'XXXX'
LIST_ID = '123456'
API_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXX'

In my src files, I've got a component that responds to a onSubmit event and constructs a URL to post a new subscriber.

(axios is used as a package for sending HTTP requests, etc)

import React, { useState } from "react"
import axios from 'axios'

const Form = () => {

    const [userEmail, setState] = useState({'email_address': ''})

    const creds = 'anystring:'+ process.env.API_KEY
    let URL = 'https://'+ process.env.DATA_NO +'.api.example.com/3.0'
    URL += '/lists/'+ process.env.LIST_ID +'/members'

    const submitSubscribe = async e => {
        e.preventDefault()

        const payload = {
            'email_address': userEmail.email_address,
            'status': 'subscribed'
        }

        try {          
            const response = await axios.post( URL , payload, {
                headers: {
                    'Authorization': 'Basic ' + Buffer.from(creds ).toString('base64')
                }
            })
            console.log('r', response)
            console.log('r data', response.data)
          } catch(err) {
            console.log(err);
          }

    }

    return (
        <form name="newsletter-signup" method="post" onSubmit={submitSubscribe}>
            {/*<input type="hidden" name="form-name" value="newsletter-signup" />*/}
            <input type="email" placeholder="Email required" onChange={handleChange} value={userEmail.email_address} required />
            <button type="submit" className="button primary-button-inverted">Send'</button>
        </form>
    )
}

So, what's happening is that on RUN time, my env vars are coming out as undefined.

I've been on the Netlify docs and they keep saying you need to interpolate the values to the client to be able to use them. I understand the logic here. These env vars need to be printed and bundled during build time, not invoked at run time.

The question I'm struggling with is HOW do I do this?

I have set up a .env.development file in the root of my project. I have tried prefixing my env vars with GATSBY_ but I still have the same trouble.

I tried using require('dotenv').config() but I'm not sure where exactly to put that (in my gatsby-node.js, gatsby-config.js) or do I need to include on the page with my component that is using these env vars.

I'd like to be able to set these vars up in one place (maybe two if testing in development) but I don't want to much tweaking involved to be able to use them in both development and production builds.

I also understand that Netlify or Gatsby can process these vars into a functions/ folder in my source code that I can somehow make use of but that seems like more than I need to just post a simple form.

Please help!

Update

Current code:

In my project root, I created two .env files, one for development and one for production. They each share the following format (remember, I am developing in GatsbyJS):

GATSBY_MC_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxx-xxxx"
GATSBY_MC_DATA_NO="xxxx"
GATSBY_MC_AUDIENCE_ID="xxxxxxxxxxx"

I've set up a separate config.js file in src/config/config.js to organize and validate my env vars (thanks @Baboo_). It looks like:

export const MC_API_KEY = process.env.GATSBY_MC_API_KEY;
export const MC_DATA_NO = process.env.GATSBY_MC_DATA_NO;
export const MC_AUDIENCE_ID = process.env.GATSBY_MC_AUDIENCE_ID;

const envVars = [
    {name: "MC_API_KEY", value: MC_API_KEY},
    {name: "MC_DATA_NO", value: MC_DATA_NO},
    {name: "MC_AUDIENCE_ID", value: MC_AUDIENCE_ID}
]

export const checkEnvVars = () => {
    const envVarsNotLoaded = envVars.filter((envVar) => envVar.value !== undefined);
    if (envVarsNotLoaded.length > 0) {
        throw new Error(`Could not load env vars ${envVarsNotLoaded.join(",")}`);
    }
}

checkEnvVars()

However, when I run gatsby develop, the "Could not load env vars" error gets thrown.

Upvotes: 1

Views: 1017

Answers (2)

Mattia Rasulo
Mattia Rasulo

Reputation: 1451

You need to add a different env file for the two environments to make this work. Meaning .env.development and .env.production.

Upvotes: 0

Baboo
Baboo

Reputation: 4258

You are doing it the right way.

What you have to do is indeed prefix your environment variables with GATSBY_, Gatsby will automatically load them. Then call them in your code:

const creds = 'anystring:'+ process.env.GATSBY_API_KEY
let URL = 'https://'+ process.env.GATSBY_DATA_NO +'.api.example.com/3.0'
tURL += '/lists/'+ process.env.GATSBY_LIST_ID +'/members'

Make sure to use the whole string process.env.GATSBY_LIST_ID instead of process.env[GATSBY_LIST_ID] because the object process.env is undefined.

Locally

Make sure to create to .env files, .env.development and .env.production. The former is used when you run gatsby develop and the latter when you run gatsby build.

You may already know that you shouldn't commit these files.

Netlify

Add the same environment variables in your deployment pipeline on Netlify. Here is the related doc. This way Netlify can build your webiste when being deployed.

Improvements

Instead of refering environment variables directly, create a file where they are loaded and if one of them cannot be retrieved, throw an error. This way you will be noticed when the loading fails and save debugging time.

Example:

// config.js
export const API_KEY = process.env.GATSBY_API_KEY;
export const DATA_NO = process.env.GATSBY_DATA_NO ;

const envVars = [
    {name: "API_KEY", value: API_KEY},
    {name: "DATA_NO", value: DATA_NO},
]

const checkEnvVars = () => {
    const envVarsNotLoaded = envVars.filter(isUndefined);
    if (envVarsNotLoaded.length > 0) {
        throw new Error(`Could not load env vars ${envVarsNotLoaded.join(",")}`);
    }
}

const isUndefined = (envVar) => typeof envVar.value === "undefined";

// component.js
import React, { useState } from "react"
import axios from 'axios'
// Import environment variables
import { API_KEY, DATA_NO } from "./config"

const Form = () => {
    // ...

    const [userEmail, setState] = useState({'email_address': ''})

    const creds = 'anystring:'+ API_KEY
    let URL = 'https://'+ DATA_NO +'.api.example.com/3.0'

Upvotes: 2

Related Questions