Skip to content

tcaty/spa-env

Repository files navigation

spa-env

Have you ever tried to use environment variables in single page applications at browser runtime? There are several ways to do it, however almost all of them are too slow, too unsafe or have redundant complexity. So, spa-env is fast, reliable and simple solution of this problem.

Introduction

Environment variables in spa

First of all there is a little note about environment variables in spa. Imagine that we have app.js file with code below:

console.log(process.env.VITE_API_URL)

And there is .env file as well:

VITE_API_URL=https://api.com/

As you've noticed we'll use vite to build this application. After running yarn build there will be built static file, which looks like this:

console.log("https://api.com/")

So, the main idea is that variables that refer on environment variables are replaced by static values at buildtime.

Suggested workflow

  1. Generate .env.production file based on .env.development file via cmd generate.
  2. Use cmd replace in Dockerfile entrypoint in order to update environment variables during container recreations.
  3. Copy and paste environment variables list from generated .env.production to docker-compose.yml.
  4. Fill environment variables values.

cmd generate

This command allows to generate .env.production file filled with placeholders based on .env.development file.

For example, some .env.development file looks like this:

# server side variables
POSTGRES_CONN_STRING=postgres://username:password@prod.server:5432/database
# client side variables
API_URL=https://api.com/
SECRET_TOKEN=54321

Then run command below:

spa-env generate \
    --workdir ./ \
    --dotenv-dev .env.development \
    --dotenv-prod .env.production \
    --key-prefix NEXT_PUBLIC \
    --placeholder-prefix PLACEHOLDER \
    --enable-comments \
    --log-level DEBUG

It will generate .env.production that looks like this:

# This file was auto-generated by spa-env tool. Don't edit it manually!
# There is a full list of environment variables sorted alphabetically below.
# It includes client side variables as well as server side variables.
# Just copy this list and paste it to app service environment in docker-compose.yml file.
#
# API_URL
# SECRET_TOKEN
# POSTGRES_CONN_STRING

# env -> API_URL
# src -> process.env.NEXT_PUBLIC_API_URL
NEXT_PUBLIC_API_URL=PLACEHOLDER_API_URL

# env -> SECRET_TOKEN
# src -> process.env.NEXT_PUBLIC_SECRET_TOKEN
NEXT_PUBLIC_SECRET_TOKEN=PLACEHOLDER_SECRET_TOKEN

Further this file could be used in combination with command replace in order to use runtime environment variables in spa.

cmd replace

Common Dockerfile for nextjs apps looks like this:

# deps stage
...

# build stage
...

# runtime stage
...

# run app
CMD ["node", "server.js"]

All spa-env usage could be injected in runtime stage in Dockerfile of target spa. So, Dockerfile for nextjs app turns into this:

# deps stage
...

# build stage
...

# runtime stage
...

# download binary from official image
COPY --from=tcaty/spa-env /spa-env /spa-env

# run binary
ENTRYPOINT [ \
    "/spa-env", "replace", \
    "--workdir", "/app", \
    "--dotenv", ".env.production", \
    "--key-prefix", "NEXT_PUBLIC", \
    "--placeholder-prefix", "PLACEHOLDER", \
    "--cmd", "node server.js", \
    "--log-level", "DEBUG" \
]

Further just copy environment variables list from generated .env.production file and paste it to docker-compose.yml file. For example, it could looks like this:

...
services:
  nextjs:
    ...
    environment:
      API_URL: "https://myapi.com"
      SECRET_TOKEN: "fksdilall990fas"
      POSTGRES_CONN_STRING: "postgres://username:password@mydbhost:5432/database"

Examples

There are two available examples in examples folder:

  • nextjs - simple nextjs app
  • react - simple react app with vite