added docker prod files
This commit is contained in:
parent
f83a6add48
commit
88a4fddf17
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@ -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
|
||||
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@ -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;"]
|
||||
29
docker-compose.yml
Normal file
29
docker-compose.yml
Normal file
@ -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
|
||||
90
nginx.conf
Normal file
90
nginx.conf
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
82
test
Normal file
82
test
Normal file
@ -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 ?
|
||||
@ -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
|
||||
},
|
||||
}));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user