'NextJS process.env.NEXT_PUBLIC variable is empty in production
I have a NextJS "^11.1.2" app, which gets build in a Dockerfile and deployed to production via CI/CD. But my process.env variables are not rendered
I have this in my client side code, which should be rendered at runtime:
const PublicApiUrl = process.env.NEXT_PUBLIC_API_URL;
In my (Gitlab) CI/CD Pipeline I added via AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS some --build-args, as well ENV and ARG:
AUTO_DEVOPS_BUILD_IMAGE_EXTRA_ARGS --build-arg=NEXT_PUBLIC_API_URL=https://my.api.com --build-arg=NEXT_PUBLIC_API_URL=https://my.api.com --build-arg=NEXT_PUBLIC_BUILDER_KEY=XXXXXX
NEXT_PUBLIC_API_URL=https://my.api.com
API_URL=https://my.api.com
Dockerfile
ARG API_URL
ENV API_URL=$API_URL
ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ARG NEXT_PUBLIC_BUILDER_KEY
ENV NEXT_PUBLIC_BUILDER_KEY=$NEXT_PUBLIC_BUILDER_KEY
RUN npm run build # which resolves in "build": "next build"
This values below are definitely picked up (I did a RUN env and can see the variables are there).
This is my configMap at Kubernetes which mounts the .env.local file into the container:
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-env-local
annotations:
"helm.sh/resource-policy": keep
data:
.env: |-
NEXT_PUBLIC_API_URL=https://my.api.url
API_URL=https://my.api.url
This is my deployment which mounts the configMap into the container as .env.local:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
labels:
app: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
volumes:
- configMap:
defaultMode: 420
items:
- key: .env
path: .env.local
name: frontend-env-local
name: frontend-env-local
imagePullSecrets:
- name: gitlab-credentials
containers:
- name: frontend
image: "registry.gitlab.com/myapp:latest"
imagePullPolicy: Always
ports:
- name: http
containerPort: 5000
protocol: TCP
volumeMounts:
- mountPath: /app/.env.local
name: frontend-env-local
readOnly: true
subPath: .env.local
When I locally build next build it works and my variable is rendered.
But when I push, build and deploy it and run the app, its an empty string:
const PublicApiUrl = "";
Why is the variable not recognized by NextJS?
I logged into production (Kubernetes pod) terminal and run env. The variables are present too.
Any ideas why this happens?
Solution 1:[1]
I had to define the variables also in my next.config.js like so:
module.exports = {
serverRuntimeConfig: {
API_URL: process.env.API_URL,
},
// Will be available on both server and client
publicRuntimeConfig: {
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
}
}
After that change it seems that neither the configMap nor the mounted volume was needed... Only the --build-arg in my CI/CD as well and the ARG and ENV in the Dockerfile
Solution 2:[2]
You don't need to define variables in your next.config.js - bad practice.
Define your env files separately
For your local:
You need to create environments folder, then create your .env.local inside this folder; here you define your local variables.
Once you did this, in your fill the package.json file, in scripts section:
"scripts": {
//you need to add this line - defining your .env.local file inside
"local": "env-cmd -f environments/.env.local node server.js",
"build": "next build",
},
so, after you start your local server with npm command as npm run local
For your server:
you need to define somehow environment variables on your server. On vercel it's in settings/environment variables
P.S. you are "missing" the value of your process.env.NEXT_PUBLIC_API_URL because .env files are normally ignored by GitHub - you newer expose your environment variables. Your file process.env.NEXT_PUBLIC_API_URL is missing on the server, it's why u have null value.
Solution 3:[3]
If you're using Docker, check if .env file is in the DockerFile before you'll make the build of the project.
Example DockerFile:
FROM node:16.14.0-alpine3.14 as build
WORKDIR /app
# Copy in only the parts needed to install dependencies
# (This avoids rebuilds if the package.json hasn’t changed)
COPY package.json .
COPY package-lock.json .
COPY .env.production .
# Install dependencies (including dev dependencies)
RUN npm install
# Copy in the rest of the project
# (include node_modules in a .dockerignore file)
COPY . .
# Build the project
RUN npm run build
# Second stage: runtime
FROM node:16.14.0-alpine3.14
WORKDIR /app
ENV NODE_ENV=production
# Again get dependencies, but this time only install
# runtime dependencies
COPY package.json .
COPY package-lock.json .
RUN npm install --only=production
# Get the built application from the first stage
COPY --from=build /app/.next/ /app/.next/
# Set runtime metadata
ENV PORT 3000
EXPOSE 3000
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Jan |
| Solution 2 | illia chill |
| Solution 3 | Nicolae Elisei |
