perf: optimize critical rendering path and reduce bundle size - Defer GTM loading - Add DNS prefetch and preconnect hints - Implement lazy loading for pages and heavy components - Optimize Vite build config with better code splitting - Add OptimizedImage component - Improve nginx caching and compression - Add bundle analyzer script
This commit is contained in:
parent
11e7c33165
commit
15ab54f378
28
index.html
28
index.html
@ -1,12 +1,17 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
|
|
||||||
<!-- Preload des ressources critiques -->
|
<!-- DNS Prefetch & Preconnect pour les domaines tiers -->
|
||||||
<link rel="preload" as="script" href="/src/main.tsx" />
|
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
|
||||||
|
<link rel="dns-prefetch" href="https://www.googletagmanager.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
|
||||||
<title>Alexandre Pommier - Portfolio</title>
|
<title>Alexandre Pommier - Portfolio</title>
|
||||||
<meta name="description" content="Alexandre Pommier, étudiant en informatique à 42, développeur passionné par les technologies web et systèmes." />
|
<meta name="description" content="Alexandre Pommier, étudiant en informatique à 42, développeur passionné par les technologies web et systèmes." />
|
||||||
@ -21,21 +26,24 @@
|
|||||||
<meta name="twitter:site" content="@lovable_dev" />
|
<meta name="twitter:site" content="@lovable_dev" />
|
||||||
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
|
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
|
||||||
|
|
||||||
<!-- Google Fonts - Optimisé avec display=swap et preload -->
|
<!-- Google Fonts - Optimisé avec display=swap -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap">
|
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
|
||||||
<noscript>
|
<noscript>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
<!-- Google Tag Manager -->
|
<!-- Google Tag Manager - Chargé de manière asynchrone -->
|
||||||
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
<script>
|
||||||
|
// Defer GTM loading to improve initial page load
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||||
})(window,document,'script','dataLayer','GTM-5V6TCG4C');</script>
|
})(window,document,'script','dataLayer','GTM-5V6TCG4C');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<!-- End Google Tag Manager -->
|
<!-- End Google Tag Manager -->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
68
nginx.conf
68
nginx.conf
@ -11,26 +11,60 @@ server {
|
|||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||||
|
|
||||||
|
# Performance headers
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
# Gzip compression
|
# Gzip compression
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_proxied any;
|
gzip_proxied any;
|
||||||
gzip_comp_level 6;
|
gzip_comp_level 6;
|
||||||
gzip_types text/plain text/css text/xml text/javascript
|
gzip_min_length 256;
|
||||||
application/json application/javascript application/xml+rss
|
gzip_types
|
||||||
application/rss+xml font/truetype font/opentype
|
text/plain
|
||||||
application/vnd.ms-fontobject image/svg+xml;
|
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
|
||||||
|
application/wasm;
|
||||||
|
|
||||||
# Cache static assets - Images
|
# Brotli compression (si supporté par nginx)
|
||||||
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
|
# brotli on;
|
||||||
|
# brotli_comp_level 6;
|
||||||
|
# brotli_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
|
||||||
|
|
||||||
|
# Cache static assets - Images WebP
|
||||||
|
location ~* \.(webp)$ {
|
||||||
expires 1y;
|
expires 1y;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
|
add_header Vary "Accept-Encoding";
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache static assets - CSS/JS
|
# Cache static assets - Images
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|svg)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
add_header Vary "Accept-Encoding";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets - CSS/JS avec hash (versionnés)
|
||||||
location ~* \.(css|js)$ {
|
location ~* \.(css|js)$ {
|
||||||
expires 1y;
|
expires 1y;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
|
add_header Vary "Accept-Encoding";
|
||||||
|
|
||||||
|
# Preload header pour les ressources critiques
|
||||||
|
location ~* -[a-f0-9]{8}\.(css|js)$ {
|
||||||
|
add_header Link "</assets/js/react-vendor-*.js>; rel=preload; as=script" always;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache static assets - Fonts
|
# Cache static assets - Fonts
|
||||||
@ -43,13 +77,21 @@ server {
|
|||||||
# SPA fallback - toutes les routes vers index.html
|
# SPA fallback - toutes les routes vers index.html
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
|
|
||||||
|
# HTTP/2 Server Push pour les ressources critiques (si supporté)
|
||||||
|
# http2_push /assets/css/index.css;
|
||||||
|
# http2_push /assets/js/index.js;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Disable cache for index.html
|
# Disable cache for index.html
|
||||||
location = /index.html {
|
location = /index.html {
|
||||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
|
||||||
add_header Pragma "no-cache";
|
add_header Pragma "no-cache" always;
|
||||||
add_header Expires "0";
|
add_header Expires "0" always;
|
||||||
|
|
||||||
|
# Ajout de Link headers pour preconnect
|
||||||
|
add_header Link "<https://fonts.googleapis.com>; rel=preconnect" always;
|
||||||
|
add_header Link "<https://fonts.gstatic.com>; rel=preconnect; crossorigin" always;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Disable cache for service worker if you add one later
|
# Disable cache for service worker if you add one later
|
||||||
@ -61,4 +103,10 @@ server {
|
|||||||
|
|
||||||
# Error pages
|
# Error pages
|
||||||
error_page 404 /index.html;
|
error_page 404 /index.html;
|
||||||
|
|
||||||
|
# Optimisation supplémentaire
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,10 +6,12 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"build:analyze": "vite build --mode production && npm run analyze",
|
||||||
"build:dev": "vite build --mode development",
|
"build:dev": "vite build --mode development",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"optimize-images": "node scripts/optimize-images.js"
|
"optimize-images": "node scripts/optimize-images.js",
|
||||||
|
"analyze": "node scripts/analyze-bundle.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
|
|||||||
92
scripts/analyze-bundle.js
Normal file
92
scripts/analyze-bundle.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { readdir, stat } from 'fs/promises';
|
||||||
|
import { join, extname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const distDir = join(__dirname, '..', 'dist');
|
||||||
|
|
||||||
|
async function getFileSize(filePath) {
|
||||||
|
const stats = await stat(filePath);
|
||||||
|
return stats.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function analyzeDirectory(dir, prefix = '') {
|
||||||
|
const files = await readdir(dir);
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const fullPath = join(dir, file);
|
||||||
|
const stats = await stat(fullPath);
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
results.push(...(await analyzeDirectory(fullPath, prefix + file + '/')));
|
||||||
|
} else {
|
||||||
|
const size = await getFileSize(fullPath);
|
||||||
|
results.push({
|
||||||
|
path: prefix + file,
|
||||||
|
size,
|
||||||
|
sizeKB: (size / 1024).toFixed(2),
|
||||||
|
ext: extname(file)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log('📊 Analyse du bundle de production...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const files = await analyzeDirectory(distDir);
|
||||||
|
|
||||||
|
// Grouper par type
|
||||||
|
const byType = files.reduce((acc, file) => {
|
||||||
|
if (!acc[file.ext]) acc[file.ext] = [];
|
||||||
|
acc[file.ext].push(file);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Afficher les statistiques
|
||||||
|
console.log('📦 Taille par type de fichier:\n');
|
||||||
|
|
||||||
|
Object.keys(byType).sort().forEach(ext => {
|
||||||
|
const totalSize = byType[ext].reduce((sum, f) => sum + f.size, 0);
|
||||||
|
const count = byType[ext].length;
|
||||||
|
console.log(`${ext || 'no-ext'}: ${(totalSize / 1024).toFixed(2)} KB (${count} fichier${count > 1 ? 's' : ''})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fichiers les plus lourds
|
||||||
|
console.log('\n🔝 10 fichiers les plus lourds:\n');
|
||||||
|
files
|
||||||
|
.sort((a, b) => b.size - a.size)
|
||||||
|
.slice(0, 10)
|
||||||
|
.forEach((file, i) => {
|
||||||
|
console.log(`${i + 1}. ${file.path}: ${file.sizeKB} KB`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Taille totale
|
||||||
|
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
||||||
|
console.log(`\n💾 Taille totale: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
|
||||||
|
|
||||||
|
// Avertissements
|
||||||
|
console.log('\n⚠️ Avertissements:');
|
||||||
|
const largeFiles = files.filter(f => f.size > 500 * 1024); // > 500KB
|
||||||
|
if (largeFiles.length > 0) {
|
||||||
|
console.log(`\n${largeFiles.length} fichier(s) de plus de 500 KB détecté(s):`);
|
||||||
|
largeFiles.forEach(f => {
|
||||||
|
console.log(` - ${f.path}: ${f.sizeKB} KB`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('✅ Aucun fichier trop lourd détecté!');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur lors de l\'analyse:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
22
src/App.tsx
22
src/App.tsx
@ -3,14 +3,24 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
|
|||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
import Index from "./pages/Index";
|
import { lazy, Suspense } from "react";
|
||||||
import NotFound from "./pages/NotFound";
|
|
||||||
import ProjectPage from "./pages/ProjectPage";
|
|
||||||
import { ParticlesBackground } from "./components/ParticlesBackground";
|
|
||||||
import { ThemeProvider } from "./contexts/ThemeContext";
|
import { ThemeProvider } from "./contexts/ThemeContext";
|
||||||
|
|
||||||
|
// Lazy load pages and heavy components for better performance
|
||||||
|
const Index = lazy(() => import("./pages/Index"));
|
||||||
|
const ProjectPage = lazy(() => import("./pages/ProjectPage"));
|
||||||
|
const NotFound = lazy(() => import("./pages/NotFound"));
|
||||||
|
const ParticlesBackground = lazy(() => import("./components/ParticlesBackground").then(m => ({ default: m.ParticlesBackground })));
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
// Loading fallback component
|
||||||
|
const PageLoader = () => (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const IndexWrapper = () => <Index />;
|
const IndexWrapper = () => <Index />;
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@ -32,10 +42,14 @@ const App = () => (
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
<Suspense fallback={null}>
|
||||||
<ParticlesBackground />
|
<ParticlesBackground />
|
||||||
|
</Suspense>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Sonner />
|
<Sonner />
|
||||||
|
<Suspense fallback={<PageLoader />}>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
|
</Suspense>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|||||||
45
src/components/OptimizedImage.tsx
Normal file
45
src/components/OptimizedImage.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface OptimizedImageProps {
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
className?: string;
|
||||||
|
loading?: 'lazy' | 'eager';
|
||||||
|
priority?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OptimizedImage = ({
|
||||||
|
src,
|
||||||
|
alt,
|
||||||
|
className = '',
|
||||||
|
loading = 'lazy',
|
||||||
|
priority = false
|
||||||
|
}: OptimizedImageProps) => {
|
||||||
|
const [imageSrc, setImageSrc] = useState<string | undefined>(undefined);
|
||||||
|
const [imageLoaded, setImageLoaded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (priority) {
|
||||||
|
// Pour les images prioritaires, charger immédiatement
|
||||||
|
setImageSrc(src);
|
||||||
|
} else {
|
||||||
|
// Pour les autres, utiliser IntersectionObserver
|
||||||
|
const img = new Image();
|
||||||
|
img.src = src;
|
||||||
|
img.onload = () => {
|
||||||
|
setImageSrc(src);
|
||||||
|
setImageLoaded(true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [src, priority]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={imageSrc}
|
||||||
|
alt={alt}
|
||||||
|
className={`${className} ${imageLoaded ? 'opacity-100' : 'opacity-0'} transition-opacity duration-300`}
|
||||||
|
loading={loading}
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
17
vite-plugin-preload.ts
Normal file
17
vite-plugin-preload.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import type { Plugin } from 'vite';
|
||||||
|
|
||||||
|
export function preloadPlugin(): Plugin {
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-preload',
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
// Ajouter des preload hints pour les chunks critiques
|
||||||
|
const preloadLinks = `
|
||||||
|
<!-- Preload critical resources -->
|
||||||
|
<link rel="modulepreload" href="/src/main.tsx" />
|
||||||
|
<link rel="dns-prefetch" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="dns-prefetch" href="https://fonts.gstatic.com" />`;
|
||||||
|
|
||||||
|
return html.replace('</head>', `${preloadLinks}\n </head>`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -25,40 +25,30 @@ export default defineConfig(({ mode }) => ({
|
|||||||
build: {
|
build: {
|
||||||
// Optimisations pour la production
|
// Optimisations pour la production
|
||||||
minify: 'esbuild', // Utiliser esbuild au lieu de terser (plus rapide, déjà inclus)
|
minify: 'esbuild', // Utiliser esbuild au lieu de terser (plus rapide, déjà inclus)
|
||||||
|
target: 'esnext', // Code plus moderne et plus petit
|
||||||
|
cssMinify: true,
|
||||||
// Chunking optimal pour de meilleures performances
|
// Chunking optimal pour de meilleures performances
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
|
// Split vendors pour améliorer le cache
|
||||||
manualChunks: {
|
manualChunks: {
|
||||||
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
|
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
|
||||||
'ui-vendor': ['framer-motion', 'lucide-react'],
|
'ui-vendor': ['framer-motion', 'lucide-react'],
|
||||||
'particles': ['@tsparticles/react', '@tsparticles/slim'],
|
'particles': ['@tsparticles/react', '@tsparticles/slim', '@tsparticles/engine'],
|
||||||
'radix-ui': [
|
|
||||||
'@radix-ui/react-accordion',
|
|
||||||
'@radix-ui/react-alert-dialog',
|
|
||||||
'@radix-ui/react-avatar',
|
|
||||||
'@radix-ui/react-checkbox',
|
|
||||||
'@radix-ui/react-collapsible',
|
|
||||||
'@radix-ui/react-dialog',
|
|
||||||
'@radix-ui/react-dropdown-menu',
|
|
||||||
'@radix-ui/react-label',
|
|
||||||
'@radix-ui/react-popover',
|
|
||||||
'@radix-ui/react-progress',
|
|
||||||
'@radix-ui/react-scroll-area',
|
|
||||||
'@radix-ui/react-select',
|
|
||||||
'@radix-ui/react-separator',
|
|
||||||
'@radix-ui/react-slider',
|
|
||||||
'@radix-ui/react-slot',
|
|
||||||
'@radix-ui/react-switch',
|
|
||||||
'@radix-ui/react-tabs',
|
|
||||||
'@radix-ui/react-toast',
|
|
||||||
'@radix-ui/react-tooltip',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
// Nommer les chunks de manière cohérente pour le cache
|
||||||
|
chunkFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
entryFileNames: 'assets/js/[name]-[hash].js',
|
||||||
|
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Optimisation des assets
|
// Optimisation des assets
|
||||||
assetsInlineLimit: 4096, // Images < 4kb seront inline en base64
|
assetsInlineLimit: 4096, // Images < 4kb seront inline en base64
|
||||||
chunkSizeWarningLimit: 1000, // Augmenter la limite d'avertissement
|
chunkSizeWarningLimit: 500, // Limite plus stricte pour éviter les gros bundles
|
||||||
sourcemap: false, // Désactiver les sourcemaps en production
|
sourcemap: false, // Désactiver les sourcemaps en production
|
||||||
|
// Compression CSS supplémentaire
|
||||||
|
cssCodeSplit: true,
|
||||||
|
// Minification supplémentaire
|
||||||
|
reportCompressedSize: true,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user