I have Kubernetes deployment of a NextJS app, I want to run the app in development mode so that updating a file causes the application to hot-reload immediately, in particular I want to update just some JSON files in the "config" folder. I created a docker container that copies the necessary files except the "config files", the config files instead are attached to the container using Kubernetes ConfigMaps, mounted as volumes.
I'm running on minikube on Windows WSL2.
When I first run the container it works fine, but if I navigate to some pages or I change values in the configmaps, I get this error (or an error with a different file like PlanModel.config.json
):
> @ dev-base /usr/src/app
> next dev
▲ Next.js 14.2.3
- Local: http://localhost:3000
✓ Starting...
Attention: Next.js now collects completely anonymous telemetry regarding usage.
This information is used to shape Next.js' roadmap and prioritize features.
You can learn more, including how to opt-out
if you'd not like to participate in this anonymous program, by visiting the following URL:https://nextjs.org/telemetry
✓ Ready in 3s
○ Compiling /middleware ...
✓ Compiled /middleware in 2.3s (216 modules) ○ Compiling / ...
Browserslist: caniuse-lite is outdated. Please run:
npx update-browserslist-db@latest
Why you should do it regularly: https://github.com/browserslist/update-db#readme
✓ Compiled / in 18.6s (4401 modules)
cookie is: undefined
GET / 200 in 19523ms
✓ Compiled in 1339ms (1847 modules)
cookie is: undefined
GET / 200 in 68ms
cookie is: undefined
GET / 200 in 46ms
cookie is: undefined
GET / 200 in 37ms
⨯ ./config/ai/..2024_09_30_15_42_23.830652425/actions.config.json
Module build failed: Error: ENOENT: no such file or directory, open '/usr/src/app/config/ai/..2024_09_30_15_42_23.830652425/actions.config.json'
Import trace for requested module:
./config/ai/..2024_09_30_15_42_23.830652425/actions.config.json
./lib/chat/actions.config.tsx
./lib/chat/actions.tsx
./app/(chat)/page.tsx
Sometimes it happened that instead of getting this error immediately the app continued working but without hot reloading.
In both cases, I opened a shell in the container and I manually verified that the files are there and are correct.
I noticed that Kubernetes created symlinks for files in configmaps. In particular, the symlinks go like this:
actions.config.json -> ..data/actions.config.json -> ..2024_09_30_15_42_23.830652425/actions.config.json
I found out that NextJS has problems with tracking symlinks in development mode, because of webpack (https://github.com/vercel/next.js/issues/53175, https://github.com/webpack/watchpack/pull/232). I don't know how to solve this issue. I think I could either user a different Kubernetes approach that doesn't create symlinks (but I still need that I can update easily the config files) or I fix this NextJS problem.
I want to build a platform that allows user to customize a NextJS application by changing some "config files", that are actually JSON files. These config files are imported (statically) by the NextJS application and determine the colors, the styles and the behavior of the application. So my users can access an editor that shows:
Instead of creating a new slightly different NextJS application from the one I already created, and in order to allow live changes, I simply decided to run it in development mode.
In any case, both the "final version" and the "preview version" must be run at a scale and I decided to use kubernetes. I created an API that dynamically generates kubernetes manifests for both versions.
What I'm having problem with is the preview version, I have this shortened example file:
apiVersion: v1
kind: Namespace
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: myId
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-environment
namespace: myId
data:
# App configuration
APP_ID: myId
# Postgres database, MongoDB database, etc env variables
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: app-config
namespace: myId
data:
theme.config.json: |
CONTENT
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: app-config-ai
namespace: myId
data:
actions.config.json: |
CONTENT
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: app-config-components
namespace: myId
data:
button-scroll-to-bottom.config.json: |
CONTENT
chat-history.config.json: |
CONTENT
chat-message-actions.config.json: |
CONTENT
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: app-config-components-ui
namespace: myId
data:
sheet.config.json: |
CONTENT
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: app
namespace: myId
spec:
replicas: 1
selector:
matchLabels:
app_id: myId
template:
metadata:
labels:
app_id: myId
app_subdomain: testapp
spec:
containers:
- name: nginx
image: my-nginx:latest
imagePullPolicy: Always
ports:
- containerPort: 80
- name: app
image: my-app-configurable:latest
imagePullPolicy: Always
envFrom:
- configMapRef:
name: app-environment
ports:
- containerPort: 3000
volumeMounts: # /usr/src/app/ is the root directory of the app in the container
- name: app-config
mountPath: /usr/src/app/config
- name: app-config-ai
mountPath: /usr/src/app/config/ai
- name: app-config-components
mountPath: /usr/src/app/config/components
- name: app-config-components-ui
mountPath: /usr/src/app/config/components/ui
volumes:
- name: app-config
configMap:
name: app-config
- name: app-config-ai
configMap:
name: app-config-ai
- name: app-config-components
configMap:
name: app-config-components
- name: app-config-components-ui
configMap:
name: app-config-components-ui
---
apiVersion: v1
kind: Service
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: nginx-service
namespace: myId
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app_id: myId
---
apiVersion: v1
kind: Service
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: app
namespace: myId
spec:
ports:
- port: 3000
protocol: TCP
targetPort: 3000
selector:
app_id: myId
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
labels:
app_id: myId
app_subdomain: testapp
name: nginx
namespace: myId
spec:
rules:
- host: testapp.zshape.ai
http:
paths:
- backend:
service:
name: nginx-service
port:
number: 80
path: /
pathType: Prefix
This is an example import of a JSON file in my NextJS app:
import rawConfig from '@/config/components/empty-screen.config.json'
interface ComponentConfig {
[key: string]: any
title?: string
description?: string
linkURL?: string
linkLabel?: string
}
const { title, description, linkURL, linkLabel }: ComponentConfig = rawConfig
export const emptyScreenConfig = {
title,
description,
linkURL,
linkLabel
}
Then the exported emptyScreenConfig
is imported in other parts of the app.
I solved this by using next dev --turbo
that uses turbopack instead of webpack. In my case, I uses the canary version of next
and the beta version of next-auth
, because some features are not supported in the current stable version.