Skip to content

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:

cd frontend
npm install

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

npm run dev

Vite starts a development server on port 5173 (the default Vite port). It exposes a URL:

  VITE v5.x.x  ready in 350 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: http://192.168.x.x:5173/

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

export VITE_API_URL=http://localhost:8080
export VITE_LANG=fr
npm run dev

Windows PowerShell

$env:VITE_API_URL = "http://localhost:8080"
$env:VITE_LANG = "fr"
npm run dev

.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:

VITE_API_URL=http://localhost:8080
VITE_LANG=fr

Production build

npm run 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:

npm run preview

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:

export CORS_ALLOWED_ORIGINS="http://localhost:9091,http://localhost:5173"

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