From 89ad87aa64a7d33c6bd263bf5aa3d4c58407ee34 Mon Sep 17 00:00:00 2001
From: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Date: Sun, 26 Apr 2026 09:05:54 +0200
Subject: [PATCH] Make `twenty-front` build env agnostic (#20055)
## Introduction
In aim to reduce and optimize the number of twenty-front build we do
during our cd process and allow twenty-front build promotion
### Build time
**Nothing is baked.** The `build/` directory is a clean, env-agnostic
artifact. `index.html` contains the empty placeholder:
```html
```
The JS bundles contain no hardcoded server URL.
---
### Deploy mode 1: Frontend served by the backend (Docker / NestJS)
1. Container starts, NestJS boots in `main.ts`
2. `generateFrontConfig()` runs, reads `process.env.SERVER_URL`
3. Rewrites `dist/front/index.html`, replacing the placeholder with:
```html
```
4. NestJS serves the static `dist/front/` directory
5. Browser loads `index.html`, `window._env_` is set before the app JS
executes
6. `src/config/index.ts` reads `window._env_.REACT_APP_SERVER_BASE_URL`
and uses it
---
### Deploy mode 2: Frontend served standalone (CDN / nginx / static
server)
1. Take the `build/` artifact as-is
2. Before serving, run at deploy time:
```bash
REACT_APP_SERVER_BASE_URL=https://api.example.com sh
./scripts/inject-runtime-env.sh
```
3. This does the same `sed` replacement on `build/index.html`
4. Serve the `build/` directory with your static server of choice
5. Same resolution in the browser:
`window._env_.REACT_APP_SERVER_BASE_URL` is picked up by
`src/config/index.ts`
---
### Fallback: no injection at all
If neither mechanism runs (e.g. local dev with `vite dev`),
`window._env_.REACT_APP_SERVER_BASE_URL` is `undefined`, and
`getDefaultUrl()` kicks in:
- **Localhost**: returns `http://localhost:3000`
- **Non-localhost**: returns same-origin (`window.location.origin`)
---
packages/twenty-docker/twenty/Dockerfile | 7 -------
packages/twenty-front/package.json | 4 ++--
packages/twenty-front/scripts/inject-runtime-env.sh | 5 +++++
packages/twenty-front/src/config/index.ts | 4 +---
packages/twenty-front/src/testing/mockedApolloClient.ts | 4 +++-
.../twenty-front/src/testing/mockedApolloCoreClient.ts | 4 +++-
packages/twenty-front/vite.config.ts | 5 -----
7 files changed, 14 insertions(+), 19 deletions(-)
diff --git a/packages/twenty-docker/twenty/Dockerfile b/packages/twenty-docker/twenty/Dockerfile
index d800270e93..e99c7097db 100644
--- a/packages/twenty-docker/twenty/Dockerfile
+++ b/packages/twenty-docker/twenty/Dockerfile
@@ -49,8 +49,6 @@ RUN yarn workspaces focus --production twenty-emails twenty-shared twenty-sdk tw
FROM common-deps AS twenty-front-build
-ARG REACT_APP_SERVER_BASE_URL
-
COPY ./packages/twenty-front /app/packages/twenty-front
COPY ./packages/twenty-front-component-renderer /app/packages/twenty-front-component-renderer
COPY ./packages/twenty-ui /app/packages/twenty-ui
@@ -138,9 +136,6 @@ USER 1000
FROM twenty-server AS twenty
-ARG REACT_APP_SERVER_BASE_URL
-ENV REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL
-
COPY --chown=1000 --from=twenty-front-build /app/packages/twenty-front/build /app/packages/twenty-server/dist/front
LABEL org.opencontainers.image.description="Twenty image with backend and frontend."
@@ -232,7 +227,6 @@ RUN mkdir -p /data/postgres /data/redis /app/packages/twenty-server/.local-stora
&& chown -R postgres:postgres /data/postgres \
&& chown 1000:1000 /data/redis /app/packages/twenty-server/.local-storage
-ARG REACT_APP_SERVER_BASE_URL
ARG APP_VERSION=0.0.0
ENV S6_KEEP_ENV=1
@@ -241,7 +235,6 @@ ENV PG_DATABASE_URL=postgres://twenty:twenty@localhost:5432/default \
REDIS_URL=redis://localhost:6379 \
STORAGE_TYPE=local \
APP_SECRET=twenty-app-dev-secret-not-for-production \
- REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL \
APP_VERSION=$APP_VERSION \
NODE_ENV=development \
NODE_PORT=2020 \
diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json
index 3464293b08..6c1caaac65 100644
--- a/packages/twenty-front/package.json
+++ b/packages/twenty-front/package.json
@@ -3,8 +3,8 @@
"private": true,
"type": "module",
"scripts": {
- "build": "NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
- "build:sourcemaps": "NODE_ENV=production VITE_BUILD_SOURCEMAP=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build && sh ./scripts/inject-runtime-env.sh",
+ "build": "NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 npx vite build",
+ "build:sourcemaps": "NODE_ENV=production VITE_BUILD_SOURCEMAP=true NODE_OPTIONS=--max-old-space-size=8192 npx vite build",
"start:prod": "NODE_ENV=production npx serve -s build",
"tsup": "npx tsup"
},
diff --git a/packages/twenty-front/scripts/inject-runtime-env.sh b/packages/twenty-front/scripts/inject-runtime-env.sh
index b0f92ae717..5cf9068fcc 100755
--- a/packages/twenty-front/scripts/inject-runtime-env.sh
+++ b/packages/twenty-front/scripts/inject-runtime-env.sh
@@ -1,5 +1,10 @@
#!/bin/sh
+if [ -z "$REACT_APP_SERVER_BASE_URL" ]; then
+ echo "Error: REACT_APP_SERVER_BASE_URL is not set."
+ exit 1
+fi
+
echo "Injecting runtime environment variables into index.html..."
CONFIG_BLOCK=$(cat << EOF
diff --git a/packages/twenty-front/src/config/index.ts b/packages/twenty-front/src/config/index.ts
index 4f5dd28e91..b31638c31e 100644
--- a/packages/twenty-front/src/config/index.ts
+++ b/packages/twenty-front/src/config/index.ts
@@ -18,6 +18,4 @@ const getDefaultUrl = () => {
};
export const REACT_APP_SERVER_BASE_URL =
- window._env_?.REACT_APP_SERVER_BASE_URL ||
- process.env.REACT_APP_SERVER_BASE_URL ||
- getDefaultUrl();
+ window._env_?.REACT_APP_SERVER_BASE_URL || getDefaultUrl();
diff --git a/packages/twenty-front/src/testing/mockedApolloClient.ts b/packages/twenty-front/src/testing/mockedApolloClient.ts
index d8ed8e66b0..b24908c0aa 100644
--- a/packages/twenty-front/src/testing/mockedApolloClient.ts
+++ b/packages/twenty-front/src/testing/mockedApolloClient.ts
@@ -1,8 +1,10 @@
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
+import { REACT_APP_SERVER_BASE_URL } from '~/config';
+
export const mockedApolloClient = new ApolloClient({
link: new HttpLink({
- uri: process.env.REACT_APP_SERVER_BASE_URL + '/metadata',
+ uri: REACT_APP_SERVER_BASE_URL + '/metadata',
}),
cache: new InMemoryCache(),
});
diff --git a/packages/twenty-front/src/testing/mockedApolloCoreClient.ts b/packages/twenty-front/src/testing/mockedApolloCoreClient.ts
index 4c6b935c3d..bf16afc57d 100644
--- a/packages/twenty-front/src/testing/mockedApolloCoreClient.ts
+++ b/packages/twenty-front/src/testing/mockedApolloCoreClient.ts
@@ -1,8 +1,10 @@
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
+import { REACT_APP_SERVER_BASE_URL } from '~/config';
+
export const mockedApolloCoreClient = new ApolloClient({
link: new HttpLink({
- uri: process.env.REACT_APP_SERVER_BASE_URL + '/graphql',
+ uri: REACT_APP_SERVER_BASE_URL + '/graphql',
}),
cache: new InMemoryCache(),
});
diff --git a/packages/twenty-front/vite.config.ts b/packages/twenty-front/vite.config.ts
index 7c733cd6e5..4e517ccc99 100644
--- a/packages/twenty-front/vite.config.ts
+++ b/packages/twenty-front/vite.config.ts
@@ -20,7 +20,6 @@ export default defineConfig(({ mode }) => {
const env = loadEnv(mode, __dirname, '');
const {
- REACT_APP_SERVER_BASE_URL,
VITE_BUILD_SOURCEMAP,
VITE_HOST,
SSL_CERT_PATH,
@@ -232,11 +231,7 @@ export default defineConfig(({ mode }) => {
envPrefix: 'REACT_APP_',
define: {
- _env_: {
- REACT_APP_SERVER_BASE_URL,
- },
'process.env': {
- REACT_APP_SERVER_BASE_URL,
IS_DEBUG_MODE,
IS_DEV_ENV: mode === 'development' ? 'true' : 'false',
},