Build frontend¶
This page explains how to build and run the React frontend outside of Docker, in order to benefit from Vite's Hot Module Replacement (HMR) during development.
Target stack¶
- React 18
- Vite 5
- Node.js 20 LTS
See Prerequisites for exact versions.
Installing dependencies¶
From the repository root:
The first install can take 30 to 90 seconds depending on your connection. The package-lock.json file is committed to guarantee reproducible installs.
Starting the Vite dev server¶
Vite starts a development server on port 5173 (the default Vite port). It exposes a URL:
Open this URL in your browser. Code changes are automatically reflected via Hot Module Replacement — the page updates without a full reload and preserves React state when possible.
Configuration via Vite environment variables¶
The frontend uses two main environment variables:
| Variable | Default | Role |
|---|---|---|
VITE_API_URL |
https://perfshop-api.perfshop.io |
Backend URL |
VITE_LANG |
fr |
Active language (fr or en) |
For local development, point VITE_API_URL at your local backend:
Unix / macOS¶
Windows PowerShell¶
.env.local file¶
A more comfortable alternative: create a frontend/.env.local file which Vite reads automatically at startup. This file is in .gitignore by default and is never committed:
Production build¶
Vite compiles the application into optimized static assets in frontend/dist/:
- Minified and tree-shaken JavaScript
- Extracted and minified CSS
- Images copied with a content hash for cache-busting
- HTML index with updated
<script>and<link>references
You can preview the production build locally:
This starts a lightweight static server that serves dist/ on a random port — useful to validate that a build optimization has not broken the application.
Runtime injection of variables¶
The frontend Dockerfile uses a trick: it builds the image only once with __VITE_API_URL__ and __VITE_LANG__ markers in place of the real values, then an env-inject.sh script executed at the ENTRYPOINT replaces them with the values of the environment variables when the container starts.
#!/bin/sh
sed -i "s|__VITE_API_URL__|${VITE_API_URL}|g" /usr/share/nginx/html/assets/*.js
sed -i "s|__VITE_LANG__|${VITE_LANG}|g" /usr/share/nginx/html/assets/*.js
exec nginx -g "daemon off;"
This allows publishing a single image and deploying it to multiple environments (dev, staging, prod, NAS, VPS) by simply changing the container's environment variables. No per-target rebuild needed.
In npm run dev mode, this mechanism is not used — Vite injects the variables directly at build time.
CORS¶
When you run the frontend on localhost:5173 and the backend on localhost:8080, the browser considers them different origins and applies the CORS policy. The backend must therefore explicitly allow the origin of the Vite dev server.
The backend's CORS_ALLOWED_ORIGINS environment variable lists the allowed origins. By default it contains http://localhost:9091 (the port exposed by Docker Compose) but not http://localhost:5173 (the Vite dev server). Add it:
Then restart the backend.
Vite proxy configuration (alternative)¶
Instead of allowing CORS on the backend side, you can configure a proxy in vite.config.js that routes the dev server's /api/* calls to the local backend:
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})
In this mode, the browser sees all requests coming from localhost:5173, so no CORS. This is the simplest method if you do not want to touch the backend configuration.
Source code structure¶
frontend/src/
├── main.jsx ← React entry point, mounts <I18nProvider> + <App>
├── App.jsx ← Routes, AppShell, mounts PedagogiqueOrchestrator
├── index.css ← Global styles
├── App.css ← App-specific styles
├── chaos-agent.js ← Frontend Chaos on the browser side (side effects at load)
├── pages/ ← Page components
│ ├── Home.jsx
│ ├── Products.jsx
│ ├── ProductDetail.jsx
│ ├── Cart.jsx
│ ├── Checkout.jsx
│ ├── Login.jsx
│ ├── Register.jsx
│ ├── Profile.jsx
│ ├── MyOrders.jsx
│ ├── OrderConfirmation.jsx
│ ├── AdminPortal.jsx
│ ├── PedagogiqueOrchestrator.jsx
│ ├── PedagogiqueOverlay.jsx
│ ├── PedagogiqueTimer.jsx
│ ├── usePedagogiqueState.js
│ ├── SearchBar.jsx
│ └── CategorySidebar.jsx
├── pedagogique/succes/
│ └── PedagogiqueSucces.jsx ← Standalone /s/:token page
├── services/
│ └── api.js ← Single API client (all fetch calls go through here)
└── i18n/
├── I18nContext.jsx ← Provider + useT hook
├── fr.json ← French dictionary (~400 keys)
└── en.json ← English dictionary
Testing the Docker build locally¶
To build the frontend image without Compose:
cd frontend
docker build -t perfshop-frontend:local .
docker run --rm -p 9091:80 \
-e VITE_API_URL=http://host.docker.internal:8080 \
-e VITE_LANG=fr \
perfshop-frontend:local
Then open http://localhost:9091 to validate that runtime injection works correctly.
See also¶
- E-commerce frontend — architecture and role of the frontend
- Build backend — for the Spring Boot side
- Running Docker Compose — everything containerized