From 43c1c9d811460d085ebd5a7a5c852edf8aec31f3 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Mon, 16 Feb 2026 17:22:56 +0100 Subject: [PATCH] updated dockerfile and improved url managing --- docker-compose.yml | 19 +++++++++--------- frontend/Dockerfile | 38 ++++++++++++++++++++++------------- frontend/src/App.tsx | 47 ++++++++++++++++++++++---------------------- 3 files changed, 57 insertions(+), 47 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6e04096..5e66a0c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.8' services: - # --- Backend Service --- + # --- Backend Service (Gunicorn + WhiteNoise) --- backend: build: context: ./backend @@ -20,37 +20,36 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=narl" - # Explicitly set priority higher than the frontend to catch /api, /admin, etc. + # Higher priority for specific backend paths - "traefik.http.routers.holy-backend.priority=20" - "traefik.http.routers.holy-backend.rule=Host(`holy.narl.io`) && (PathPrefix(`/api`) || PathPrefix(`/admin`) || PathPrefix(`/static`) || PathPrefix(`/media`))" - "traefik.http.routers.holy-backend.entrypoints=websecure" - "traefik.http.routers.holy-backend.tls.certresolver=https_resolver" - "traefik.http.services.holy-backend.loadbalancer.server.port=8000" - # --- Frontend Service --- + # --- Frontend Service (Nginx serving built React app) --- frontend: build: context: ./frontend + args: + # Bake the API URL into the production build + - VITE_API_URL=https://holy.narl.io/api/ container_name: holy-frontend restart: unless-stopped networks: - narl - env_file: - - ./.env - volumes: - - ./frontend:/app - - /app/node_modules + # In production, we don't mount volumes for code to ensure container integrity depends_on: - backend labels: - "traefik.enable=true" - "traefik.docker.network=narl" - # Frontend acts as the fallback for anything else on this domain + # Lower priority acts as catch-all for the domain - "traefik.http.routers.holy-frontend.priority=10" - "traefik.http.routers.holy-frontend.rule=Host(`holy.narl.io`)" - "traefik.http.routers.holy-frontend.entrypoints=websecure" - "traefik.http.routers.holy-frontend.tls.certresolver=https_resolver" - - "traefik.http.services.holy-frontend.loadbalancer.server.port=5173" + - "traefik.http.services.holy-frontend.loadbalancer.server.port=80" networks: narl: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 9899f66..acbe73a 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,20 +1,30 @@ -# Use an official Node runtime as a parent image -FROM node:lts-alpine - -# Set the working directory in the container +# --- Build Stage --- +FROM node:lts-alpine AS build-stage WORKDIR /app - -# Copy package.json and package-lock.json COPY package*.json ./ - -# Install dependencies RUN npm install - -# Copy the rest of the frontend code COPY . . -# Expose the port the app runs on -EXPOSE 5173 +# Pass VITE environment variables during build +ARG VITE_API_URL +ENV VITE_API_URL=${VITE_API_URL} -# Start the development server -CMD ["npm", "run", "dev", "--", "--host"] +RUN npm run build + +# --- Production Stage --- +FROM nginx:stable-alpine +# Copy built files +COPY --from=build-stage /app/dist /usr/share/nginx/html + +# Custom Nginx config to handle React Router (SPA) +RUN printf 'server {\n\ + listen 80;\n\ + location / {\n\ + root /usr/share/nginx/html;\n\ + index index.html index.htm;\n\ + try_files $uri $uri/ /index.html;\n\ + }\n\ +}' > /etc/nginx/conf.d/default.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a4e44b1..e0d2976 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -32,7 +32,7 @@ import ExpandMore from '@mui/icons-material/ExpandMore'; import { getTheme, type CatppuccinTheme } from './theme'; import api from './api'; -// Code splitting for routes +// Lazy load components for production performance const CategoryList = lazy(() => import('./pages/CategoryList')); const CategoryFlavors = lazy(() => import('./pages/categories/CategoryFlavors')); const FlavorDetail = lazy(() => import('./pages/flavors/FlavorDetail')); @@ -58,7 +58,7 @@ const GlobalSearch = () => { const fetchFlavors = async () => { try { const res = await api.get('flavors/'); - setOptions(res.data); + setOptions(Array.isArray(res.data) ? res.data : (res.data.results || [])); } catch (err) { console.error('Failed to fetch search options'); } @@ -104,7 +104,7 @@ const GlobalSearch = () => { { useEffect(() => { const fetchData = async () => { try { + const token = localStorage.getItem('token'); const [userRes, catRes] = await Promise.all([ - localStorage.getItem('token') ? api.get('users/me/') : Promise.resolve({ data: null }), + token ? api.get('users/me/') : Promise.resolve({ data: null }), api.get('categories/') ]); - if (userRes.data) { + if (userRes.data && userRes.data.username) { setUser(userRes.data); if (userRes.data.theme) setThemeName(userRes.data.theme); + } else { + setUser(null); } - setCategories(catRes.data); + + const catData = Array.isArray(catRes.data) ? catRes.data : (catRes.data.results || []); + setCategories(catData); } catch (err) { setUser(null); } finally { @@ -199,7 +204,7 @@ const App: React.FC = () => { - {user && !user.avatar && user.username.charAt(0).toUpperCase()} + {user?.username && !user.avatar && user.username.charAt(0).toUpperCase()} setDrawerOpen(false)} style={{ color: 'inherit', textDecoration: 'none' }}>Holy Flavors @@ -277,7 +282,6 @@ const App: React.FC = () => { zIndex: (theme) => theme.zIndex.drawer + 1 }}> - {/* Brand Logo - Responsive */} Holy Flavors @@ -285,7 +289,6 @@ const App: React.FC = () => { - {/* Categories Dropdown - Desktop Only */} } /> +