From 88a4fddf1773537149fe9184c0d2af190cb48573 Mon Sep 17 00:00:00 2001 From: kinou-p Date: Thu, 2 Oct 2025 16:54:31 +0200 Subject: [PATCH] added docker prod files --- .dockerignore | 15 ++++++++ Dockerfile | 31 ++++++++++++++++ docker-compose.yml | 29 +++++++++++++++ nginx.conf | 90 ++++++++++++++++++++++++++++++++++++++++++++++ test | 82 ++++++++++++++++++++++++++++++++++++++++++ vite.config.ts | 25 +++++++++++++ 6 files changed, 272 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 nginx.conf create mode 100644 test diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9ba210f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +npm-debug.log +.git +.gitignore +.env +.env.local +.env*.local +dist +README.md +.vscode +.idea +*.md +.DS_Store +*.log +test diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5364d05 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# Étape 1: Build +FROM node:18-alpine AS builder + +WORKDIR /app + +# Copier les fichiers de dépendances +COPY package*.json ./ + +# Installer les dépendances +RUN npm ci --only=production + +# Copier le code source +COPY . . + +# Build l'application en mode production +RUN npm run build + +# Étape 2: Production avec Nginx +FROM nginx:alpine + +# Copier les fichiers buildés depuis l'étape builder +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copier la configuration Nginx optimisée +COPY nginx.conf /etc/nginx/nginx.conf + +# Exposer le port 80 +EXPOSE 80 + +# Démarrer Nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6deff57 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +services: + portfolio-website: + restart: unless-stopped + build: + context: . + dockerfile: Dockerfile + container_name: portfolio-website + networks: + - portfolio + labels: + - "traefik.enable=true" + + # Configuration HTTPS + - "traefik.http.routers.portfolio-website.rule=Host(`alexandre-pommier.com`) || Host(`www.alexandre-pommier.com`)" + - "traefik.http.routers.portfolio-website.entrypoints=websecure" + - "traefik.http.routers.portfolio-website.tls=true" + - "traefik.http.routers.portfolio-website.tls.certresolver=letsencrypt" + - "traefik.http.services.portfolio-website.loadbalancer.server.port=80" + + # Redirection HTTP vers HTTPS + - "traefik.http.routers.portfolio-website-http.rule=Host(`alexandre-pommier.com`) || Host(`www.alexandre-pommier.com`)" + - "traefik.http.routers.portfolio-website-http.entrypoints=web" + - "traefik.http.routers.portfolio-website-http.middlewares=redirect-to-https" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true" + +networks: + portfolio: + external: true diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..6d91a23 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,90 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + # Performance optimizations + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript + application/json application/javascript application/xml+rss + application/rss+xml font/truetype font/opentype + application/vnd.ms-fontobject image/svg+xml; + + server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Cache static assets - Images + location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Cache static assets - CSS/JS + location ~* \.(css|js)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Cache static assets - Fonts + location ~* \.(woff|woff2|ttf|otf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + add_header Access-Control-Allow-Origin "*"; + } + + # SPA fallback - toutes les routes vers index.html + location / { + try_files $uri $uri/ /index.html; + } + + # Disable cache for index.html + location = /index.html { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Disable cache for service worker if you add one later + location = /service-worker.js { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Error pages + error_page 404 /index.html; + } +} diff --git a/test b/test new file mode 100644 index 0000000..410943d --- /dev/null +++ b/test @@ -0,0 +1,82 @@ +Parfait 👌 je vois ton `docker-compose.yml` : là tu fais tourner ton **dev server Vite** dans un conteneur, ce qui explique tes 14s de LCP/FCP. +En prod, il faut **builder une image optimisée**, puis la servir avec **Nginx** (ou Caddy) plutôt qu’avec `npm run dev`. + +--- + +### ✅ Version corrigée pour la prod + +Voici un exemple de `docker-compose.yml` adapté : + +```yaml +version: '3.8' + +services: + react_app: + restart: unless-stopped + build: + context: . + dockerfile: Dockerfile + container_name: react_app + + labels: + - "traefik.enable=true" + - "traefik.http.routers.react_app.rule=Host(`alexandre-pommier.com`)" + - "traefik.http.routers.react_app.entrypoints=https" + - "traefik.http.routers.react_app.tls=true" + - "traefik.http.services.react_app.loadbalancer.server.port=80" + + networks: + - react + +networks: + react: + external: true +``` + +Et tu ajoutes un **Dockerfile multi-stage** à la racine de ton projet : + +```dockerfile +# Étape build +FROM node:20 AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm install --frozen-lockfile +COPY . . +RUN npm run build + +# Étape serve (nginx) +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +--- + +### ⚡️ Explication + +* **`npm run build`** → génère des fichiers statiques optimisés dans `/app/dist`. +* **Nginx** → sert ces fichiers (beaucoup plus rapide que `vite dev`). +* **Traefik** → envoie le trafic vers ton conteneur (port `80` interne, pas besoin de `8080`). +* Tu peux ajouter un fichier `nginx.conf` custom si tu veux gérer le cache, gzip/brotli, etc. + +--- + +### 🚀 Commandes + +1. Construis l’image : + + ```bash + docker compose build + ``` +2. Lance-la : + + ```bash + docker compose up -d + ``` + +Ton site sera servi en **mode prod optimisé** avec un LCP/FCP qui devrait tomber à ~1–2 secondes 🎯 + +--- + +👉 Veux-tu que je t’ajoute aussi un **nginx.conf optimisé** (cache, compression, HTTP/2 push) pour booster encore plus les Core Web Vitals ? diff --git a/vite.config.ts b/vite.config.ts index 91fab45..1c7d610 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,4 +22,29 @@ export default defineConfig(({ mode }) => ({ "@": path.resolve(__dirname, "./src"), }, }, + build: { + // Optimisations pour la production + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, // Retire les console.log en production + drop_debugger: true, + }, + }, + // Chunking optimal pour de meilleures performances + rollupOptions: { + output: { + manualChunks: { + 'react-vendor': ['react', 'react-dom', 'react-router-dom'], + 'ui-vendor': ['framer-motion', 'lucide-react'], + 'particles': ['@tsparticles/react', '@tsparticles/slim'], + 'radix-ui': Object.keys(require('./package.json').dependencies).filter(key => key.startsWith('@radix-ui')), + }, + }, + }, + // Optimisation des assets + assetsInlineLimit: 4096, // Images < 4kb seront inline en base64 + chunkSizeWarningLimit: 1000, // Augmenter la limite d'avertissement + sourcemap: false, // Désactiver les sourcemaps en production + }, }));