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', },